package io.aiven.klaw;

import static io.aiven.klaw.error.KlawErrorMessages.TOPICS_VLD_ERR_122;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import io.aiven.klaw.dao.Env;
import io.aiven.klaw.dao.Topic;
import io.aiven.klaw.dao.TopicRequest;
import io.aiven.klaw.model.enums.PermissionType;
import io.aiven.klaw.model.enums.RequestOperationType;
import io.aiven.klaw.model.enums.RequestStatus;
import io.aiven.klaw.model.requests.TopicCreateRequestModel;
import io.aiven.klaw.model.response.EnvParams;
import io.aiven.klaw.service.CommonUtilsService;
import io.aiven.klaw.service.MailUtils;
import io.aiven.klaw.service.TopicControllerService;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validator;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.TestPropertySource;

@SpringBootTest(
    webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
    classes = UiapiApplication.class)
@TestPropertySource(locations = "classpath:test-application-rdbms.properties")
@AutoConfigureMockMvc
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@DirtiesContext
public class TopicRequestValidatorImplIT {

  public static final String KWUSER = "kwuser";
  @Autowired private Validator validator;

  @MockBean private CommonUtilsService commonUtilsService;

  @MockBean private TopicControllerService topicControllerService;

  @MockBean private MailUtils mailService;

  private UtilMethods utilMethods;

  @Mock private UserDetails userDetails;

  @BeforeEach
  public void mockDefaults() {
    utilMethods = new UtilMethods();
    loginMock();
  }

  @Test
  @Order(2)
  public void isValidTestTenantFiltering() {
    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn(KWUSER);
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("2"));
    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString())
        .contains("Failure. Not authorized to request topic for this environment.");
  }

  @Test
  @Order(3)
  public void isValidTestTopicName() {
    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn(KWUSER);
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));

    addTopicRequest.setTopicname("");
    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(2);
    assertThat(violations.toString())
        .contains("Failure. Please fill in a valid topic name.", "Invalid topic name");

    addTopicRequest.setTopicname("testtopic$@$@#$"); // with special characters
    when(commonUtilsService.getTopicsForTopicName(anyString(), anyInt()))
        .thenReturn(Collections.emptyList());
    when(topicControllerService.getEnvDetails(anyString()))
        .thenReturn(utilMethods.getEnvListsIncorrect1().get(0));
    violations = validator.validate(addTopicRequest);
    assertThat(violations.toString()).contains("Invalid topic name");
  }

  @Test
  @Order(4)
  public void isValidTestVerifyTenantConfigExists() {
    Integer tenantId = 1;
    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn(KWUSER);
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(topicControllerService.getSyncCluster(anyInt()))
        .thenThrow(new RuntimeException("Sync cluster not configured"));

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString())
        .contains("Failure. Tenant configuration in Server config is missing. Please configure.");
  }

  @Test
  @Order(5)
  public void isValidTestTopicOwnedByDifferentTeam() {
    Integer tenantId = 101;
    Topic topic = utilMethods.getTopic("testtopic");

    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(commonUtilsService.getTopicsForTopicName(anyString(), anyInt()))
        .thenReturn(List.of(topic));
    when(commonUtilsService.getTeamId(anyString())).thenReturn(101);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString()).contains("Failure. This topic is owned by a different team.");
  }

  @Test
  @Order(6)
  public void isValidTestPromotionOfTopic() {
    Integer tenantId = 101;
    int teamId = 1001;
    Topic topic = utilMethods.getTopic("testtopic");
    topic.setEnvironment("2");
    topic.setTeamId(teamId);
    Env env = utilMethods.getEnvLists().get(0);

    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    addTopicRequest.setRequestOperationType(RequestOperationType.PROMOTE);
    addTopicRequest.setEnvironment("2");
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getTeamId(anyString())).thenReturn(teamId);
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1", "2"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(commonUtilsService.getTopicsForTopicName(anyString(), anyInt()))
        .thenReturn(Collections.emptyList());
    when(topicControllerService.getSyncCluster(anyInt())).thenReturn("1");
    when(commonUtilsService.getEnvProperty(anyInt(), anyString())).thenReturn("1,2");
    when(topicControllerService.getEnvDetails(anyString())).thenReturn(env);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString())
        .contains("Failure. Please request for a topic first in " + env.getName() + " cluster.");

    when(commonUtilsService.getTopicsForTopicName(anyString(), anyInt()))
        .thenReturn(List.of(topic));
    when(topicControllerService.getEnvDetails(anyString())).thenReturn(null);
    violations = validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString()).contains("Failure. Base cluster is not configured.");

    when(topicControllerService.getEnvDetails(anyString())).thenReturn(env);
    violations = validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString())
        .contains("Failure. This topic does not exist in " + env.getName() + " cluster.");
  }

  @Test
  @Order(7)
  public void isValidTestValidateTopicConfigParams() {
    Integer tenantId = 101;
    Env env = utilMethods.getEnvLists().get(0);
    String topicPrefixSuffix = "devkafka";
    //    env.setOtherParams("topic.prefix=" + topicPrefixSuffix);
    env.setParams(new EnvParams());
    env.getParams().setTopicPrefix(List.of(topicPrefixSuffix));
    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(topicControllerService.getEnvDetails(anyString())).thenReturn(env);
    when(commonUtilsService.getTeamId(anyString())).thenReturn(101);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString())
        .contains("Topic prefix does not match. " + addTopicRequest.getTopicname());

    env.setOtherParams("topic.suffix=" + topicPrefixSuffix);
    env.getParams().setTopicPrefix(null);
    env.getParams().setTopicSuffix(List.of(topicPrefixSuffix));
    when(topicControllerService.getEnvDetails(anyString())).thenReturn(env);

    violations = validator.validate(addTopicRequest);
    assertThat(violations.toString())
        .contains("Topic suffix does not match. " + addTopicRequest.getTopicname());
  }

  @Test
  @Order(8)
  public void isValidTestVerifyIfTopicRequestAlreadyExists() {
    int tenantId = 101;
    Topic topic = utilMethods.getTopic("testtopic");
    topic.setTeamId(1001);

    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    TopicRequest topicRequest = utilMethods.getTopicRequest(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(topicControllerService.getEnvDetails(anyString()))
        .thenReturn(utilMethods.getEnvLists().get(0));
    when(topicControllerService.getExistingTopicRequests(addTopicRequest, tenantId))
        .thenReturn(List.of(topicRequest));
    when(topicControllerService.existsTopicRequests(addTopicRequest, tenantId)).thenReturn(true);
    when(commonUtilsService.getTeamId(anyString())).thenReturn(101);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString()).contains("Failure. A topic request already exists.");
  }

  @Test
  @Order(9)
  public void isValidTestVerifyIfTopicRequestAlreadyExistsAndIsEditRequest() {
    int tenantId = 101;
    Topic topic = utilMethods.getTopic("testtopic");
    topic.setTeamId(1001);

    TopicCreateRequestModel editTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    editTopicRequest.setRequestId(1010);
    TopicRequest topicRequest = utilMethods.getTopicRequest(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(topicControllerService.getEnvDetails(anyString()))
        .thenReturn(utilMethods.getEnvLists().get(0));
    when(topicControllerService.getExistingTopicRequests(editTopicRequest, tenantId))
        .thenReturn(List.of(topicRequest));
    TopicRequest topicRequest1 = List.of(topicRequest).get(0);
    topicRequest1.setRequestStatus(RequestStatus.CREATED.value);
    when(topicControllerService.getTopicRequestFromTopicId(
            editTopicRequest.getRequestId(), tenantId))
        .thenReturn(topicRequest1);
    when(commonUtilsService.getTeamId(anyString())).thenReturn(101);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(editTopicRequest);
    assertThat(violations).hasSize(0);
  }

  @Test
  @Order(10)
  public void submitEditTopicRequestForDeleteTypeFailure() {
    int tenantId = 101;
    Topic topic = utilMethods.getTopic("testtopic");
    topic.setTeamId(1001);

    TopicCreateRequestModel editTopicRequest = utilMethods.getTopicCreateRequestModel(1001);
    editTopicRequest.setRequestId(1010);
    editTopicRequest.setRequestOperationType(RequestOperationType.DELETE);
    TopicRequest topicRequest = utilMethods.getTopicRequest(1001);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(topicControllerService.getEnvDetails(anyString()))
        .thenReturn(utilMethods.getEnvLists().get(0));
    when(topicControllerService.getExistingTopicRequests(editTopicRequest, tenantId))
        .thenReturn(List.of(topicRequest));
    when(commonUtilsService.getTeamId(anyString())).thenReturn(101);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(editTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString()).contains(TOPICS_VLD_ERR_122);
  }

  @Test
  @Order(11)
  public void isValidTestVerifyIfTopicAlreadyExists() {
    int tenantId = 101;
    Topic topic = utilMethods.getTopic("testtopic1001");
    int teamId = 1001;
    topic.setTeamId(teamId);
    topic.setEnvironment("1");

    TopicCreateRequestModel addTopicRequest = utilMethods.getTopicCreateRequestModel(teamId);
    when(commonUtilsService.isNotAuthorizedUser(any(), any(PermissionType.class)))
        .thenReturn(false);
    when(topicControllerService.getUserName()).thenReturn("superadmin");
    when(commonUtilsService.getTeamId(anyString())).thenReturn(teamId);
    when(commonUtilsService.getEnvsFromUserId(any())).thenReturn(Set.of("1"));
    when(commonUtilsService.getTenantId(any())).thenReturn(tenantId);
    when(topicControllerService.getEnvDetails(anyString()))
        .thenReturn(utilMethods.getEnvLists().get(0));
    when(commonUtilsService.getTopicsForTopicName(anyString(), anyInt()))
        .thenReturn(List.of(topic));
    when(topicControllerService.getExistingTopicRequests(any(), anyInt()))
        .thenReturn(Collections.emptyList());
    when(topicControllerService.existsTopicRequests(any(), anyInt())).thenReturn(false);

    Set<ConstraintViolation<TopicCreateRequestModel>> violations =
        validator.validate(addTopicRequest);
    assertThat(violations).hasSize(1);
    assertThat(violations.toString())
        .contains("Failure. This topic already exists in the selected cluster.");
  }

  private void loginMock() {
    Authentication authentication = Mockito.mock(Authentication.class);
    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    when(securityContext.getAuthentication()).thenReturn(authentication);
    when(authentication.getPrincipal()).thenReturn(userDetails);
    SecurityContextHolder.setContext(securityContext);
  }
}
