package io.aiven.klaw.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import io.aiven.klaw.config.ManageDatabase;
import io.aiven.klaw.dao.Approval;
import io.aiven.klaw.dao.Team;
import io.aiven.klaw.dao.UserInfo;
import io.aiven.klaw.error.KlawBadRequestException;
import io.aiven.klaw.error.KlawException;
import io.aiven.klaw.helpers.db.rdbms.HandleDbRequestsJdbc;
import io.aiven.klaw.model.enums.ApprovalType;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.util.ReflectionTestUtils;

@ExtendWith(SpringExtension.class)
public class ApprovalServiceTest {

  public static final String JAMES = "James";
  public static final String JACKIE = "Jackie";
  public static final String JOHN = "John";
  public static final String OCTOPUS = "Octopus";
  public static final String ALICE = "ALICE";
  public static final String SUPPORT_TEAM = "SUPPORT_TEAM";
  @Mock ManageDatabase manageDatabase;

  @Mock private MailUtils mailService;
  @Mock private CommonUtilsService commonUtilsService;

  @Mock private HandleDbRequestsJdbc handleDbRequestsJdbc;

  @InjectMocks private ApprovalService approvalService;

  @BeforeEach
  public void setUp() throws Exception {
    ReflectionTestUtils.setField(approvalService, "manageDatabase", manageDatabase);
    ReflectionTestUtils.setField(approvalService, "mailService", mailService);
    ReflectionTestUtils.setField(approvalService, "commonUtilsService", commonUtilsService);
    ReflectionTestUtils.setField(approvalService, "allowMultiApproval", true);

    when(manageDatabase.selectAllCachedUserInfo())
        .thenReturn(List.of(createUser(JOHN, 10), createUser(JAMES, 11), createUser(JACKIE, 13)));
    when(manageDatabase.getTeamNameFromTeamId(eq(101), eq(11))).thenReturn(OCTOPUS);
    when(manageDatabase.getTeamNameFromTeamId(eq(101), eq(10))).thenReturn(ALICE);
    when(manageDatabase.getTeamNameFromTeamId(eq(101), eq(13))).thenReturn(SUPPORT_TEAM);

    // reverse get teamId From TEamName
    when(manageDatabase.getTeamIdFromTeamName(eq(101), eq(OCTOPUS))).thenReturn(11);
    when(manageDatabase.getTeamIdFromTeamName(eq(101), eq(ALICE))).thenReturn(10);
    when(manageDatabase.getTeamIdFromTeamName(eq(101), eq(SUPPORT_TEAM))).thenReturn(13);

    when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);

    // setup team emails
    when(handleDbRequestsJdbc.getTeamDetails(eq(10), eq(101))).thenReturn(createTeam(ALICE, 10));
    when(handleDbRequestsJdbc.getTeamDetails(eq(11), eq(101))).thenReturn(createTeam(OCTOPUS, 11));
    when(handleDbRequestsJdbc.getTeamDetails(eq(13), eq(101)))
        .thenReturn(createTeam(SUPPORT_TEAM, 13));
  }

  @Test
  public void addAResourceOwnerApproval() throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(OCTOPUS, 1, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JAMES, 11, null);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(0).getApproverName()).isEqualTo(JAMES);
    assertThat(approvalAdded.get(0).getApproverTeamId()).isEqualTo(11);
    assertThat(approvalAdded.get(0).getApproverTeamName()).isEqualTo(OCTOPUS);
    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addAResourceOwnerApprovalAndAttemptASecond()
      throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(OCTOPUS, 1, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JAMES, 11, null);
    assertThatThrownBy(() -> approvalService.addApproval(approvalAdded, JAMES, 11, null))
        .isInstanceOf(KlawBadRequestException.class);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(0).getApproverName()).isEqualTo(JAMES);
    assertThat(approvalAdded.get(0).getApproverTeamId()).isEqualTo(11);
    assertThat(approvalAdded.get(0).getApproverTeamName()).isEqualTo(OCTOPUS);
    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addAResourceOwnerApproval_rejectDifferentTeams()
      throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(OCTOPUS, 1, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JOHN, 11, null);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(0).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(0).getApproverTeamName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addAResourceOwnerApproval_rejectUserAlreadyApproved() throws KlawException {
    List<Approval> approvals =
        List.of(
            createApproval(OCTOPUS, 1, ApprovalType.TOPIC_TEAM_OWNER, JAMES, OCTOPUS, 11),
            createApproval(OCTOPUS, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));
    assertThatThrownBy(() -> approvalService.addApproval(approvals, JAMES, 11, null))
        .isInstanceOf(KlawBadRequestException.class);

    assertThat(approvals.size()).isEqualTo(2);

    assertThat(approvals.get(0).getApproverName()).isEqualTo(JAMES);
    assertThat(approvals.get(0).getApproverTeamId()).isEqualTo(11);
    assertThat(approvals.get(0).getApproverTeamName()).isEqualTo(OCTOPUS);
    assertThat(approvals.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvals.get(1).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvals)).isFalse();
  }

  @Test
  public void addAnACLOwnerApproval_WhereDifferentTeamOwnsResource()
      throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JOHN, 11, 10);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(1).getApproverName()).isEqualTo(JOHN);
    assertThat(approvalAdded.get(1).getApproverTeamId()).isEqualTo(10);
    assertThat(approvalAdded.get(1).getApproverTeamName()).isEqualTo(ALICE);
    assertThat(approvalAdded.get(0).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(0).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addAnACLOwnerApproval_SameTeamOwnsSubscriptionAndResourceTeams()
      throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JOHN, 10, 10);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(1).getApproverName()).isEqualTo(JOHN);
    assertThat(approvalAdded.get(1).getApproverTeamId()).isEqualTo(10);
    assertThat(approvalAdded.get(1).getApproverTeamName()).isEqualTo(ALICE);
    assertThat(approvalAdded.get(1).getApprovalType()).isEqualTo(ApprovalType.ACL_TEAM_OWNER);
    assertThat(approvalAdded.get(0).getApproverName()).isEqualTo(JOHN);
    assertThat(approvalAdded.get(0).getApproverTeamId()).isEqualTo(10);
    assertThat(approvalAdded.get(0).getApproverTeamName()).isEqualTo(ALICE);

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isTrue();
  }

  @Test
  public void addAnACLOwnerApproval_SameTeamOwnsSubscriptionAndResourceTeams_NoApproval()
      throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JAMES, 10, 10);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();
    assertThat(approvalAdded.get(0).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(0).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addATeamOwnerApprovalToAcl_AddTeamApproval()
      throws KlawException, KlawBadRequestException {

    List<Approval> approvals =
        List.of(
            createApproval(SUPPORT_TEAM, 1, ApprovalType.TEAM, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JACKIE, 10, 11);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(0).getApproverName()).isEqualTo(JACKIE);
    assertThat(approvalAdded.get(0).getApproverTeamId()).isEqualTo(13);
    assertThat(approvalAdded.get(0).getApproverTeamName()).isEqualTo(SUPPORT_TEAM);
    assertThat(approvalAdded.get(0).getApprovalType()).isEqualTo(ApprovalType.TEAM);
    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();
    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addATeamOwnerApproval_AddTeamApproval()
      throws KlawException, KlawBadRequestException {

    List<Approval> approvals =
        List.of(
            createApproval(SUPPORT_TEAM, 1, ApprovalType.TEAM, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JACKIE, 10, null);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(0).getApproverName()).isEqualTo(JACKIE);
    assertThat(approvalAdded.get(0).getApproverTeamId()).isEqualTo(13);
    assertThat(approvalAdded.get(0).getApproverTeamName()).isEqualTo(SUPPORT_TEAM);
    assertThat(approvalAdded.get(0).getApprovalType()).isEqualTo(ApprovalType.TEAM);
    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();
    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void addATeamOwnerApproval_rejectDifferentTeams_NoApprovalsAdded()
      throws KlawException, KlawBadRequestException {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    List<Approval> approvalAdded = approvalService.addApproval(approvals, JACKIE, 10, 10);

    assertThat(approvalAdded.size()).isEqualTo(2);

    assertThat(approvalAdded.get(1).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(1).getApproverTeamName()).isNullOrEmpty();
    assertThat(approvalAdded.get(0).getApproverName()).isNullOrEmpty();
    assertThat(approvalAdded.get(0).getApproverTeamName()).isNullOrEmpty();

    // IsNotFullyApproved They should only be allowed to approve one approval slot of the same type
    // in this case RESOURCE_TEAM_OWNER
    assertThat(approvalService.isRequestFullyApproved(approvalAdded)).isFalse();
  }

  @Test
  public void getEmailAddresses() {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));

    Set<String> emails = approvalService.getApproverEmails(approvals, 101);
    // two seperate email addresses expected
    assertThat(emails.size()).isEqualTo(2);
  }

  @Test
  public void getEmailAddresses_DuplicateTeamsOnlyOneEmailAddressReturned() {

    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, null, null, null),
            createApproval(ALICE, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(ALICE, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null),
            createApproval(ALICE, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));

    Set<String> emails = approvalService.getApproverEmails(approvals, 101);
    // All The same Team so we only expect to get one email so we can send once.
    assertThat(emails.size()).isEqualTo(1);
  }

  @Test
  public void sendEmailToApprovers() {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, null, null, null),
            createApproval(ALICE, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null),
            createApproval(ALICE, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null),
            createApproval(ALICE, 2, ApprovalType.TOPIC_TEAM_OWNER, null, null, null));

    approvalService.sendEmailToApprovers(
        JOHN, "SimpleTopic", null, null, MailUtils.MailType.TOPIC_CREATE_REQUESTED, approvals, 101);
  }

  @Test
  public void isUserAbleToApprove_NoApprovalsLeft_returnFalse() {
    assertThat(approvalService.isUserAQualifiedOutstandingApprover(new ArrayList<>(), JACKIE))
        .isFalse();
  }

  @Test
  public void isUserAbleToApprove_NotElligible_returnFalse() {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, null, null, null),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    assertThat(approvalService.isUserAQualifiedOutstandingApprover(approvals, JACKIE)).isFalse();
  }

  @Test
  public void isUserAbleToApprove_AlreadyAddedApproval_returnFalse() {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, JACKIE, SUPPORT_TEAM, 13),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    assertThat(approvalService.isUserAQualifiedOutstandingApprover(approvals, JACKIE)).isFalse();
  }

  @Test
  public void isUserAbleToApprove_AlreadyAddedApprovalAsMemberOfAnotherTeam_returnFalse() {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, JACKIE, OCTOPUS, 11),
            createApproval(SUPPORT_TEAM, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    assertThat(approvalService.isUserAQualifiedOutstandingApprover(approvals, JACKIE)).isFalse();
  }

  @Test
  public void isUserAbleToApprove_AlreadyAddedApproval_returnTrue() {
    List<Approval> approvals =
        List.of(
            createApproval(ALICE, 1, ApprovalType.TEAM, JACKIE, SUPPORT_TEAM, 13),
            createApproval(OCTOPUS, 2, ApprovalType.ACL_TEAM_OWNER, null, null, null));
    assertThat(approvalService.isUserAQualifiedOutstandingApprover(approvals, JACKIE)).isFalse();
  }

  private UserInfo createUser(String userName, int teamId) {
    UserInfo user = new UserInfo();
    user.setUsername(userName);
    user.setRole("USER");
    user.setTeamId(teamId);
    user.setMailid(userName + "@klaw.mailid");
    user.setTenantId(101);

    return user;
  }

  private Approval createApproval(
      String requiredTeamName,
      Integer approvalId,
      ApprovalType type,
      String userName,
      String approverTeamName,
      Integer approvingTeamId) {
    Approval approval = new Approval();
    approval.setApproverName(userName);
    approval.setApprovalId(approvalId);
    approval.setApprovalType(type);
    approval.setApproverTeamName(approverTeamName);
    approval.setRequiredApprover(requiredTeamName);
    if (approvingTeamId != null) {
      approval.setApproverTeamId(approvingTeamId);
    }

    return approval;
  }

  private static Team createTeam(String teamName, int teamId) {
    Team t = new Team();
    t.setTeamId(teamId);
    t.setTeammail(teamName + ".klaw@mailid");
    t.setTeamname(teamName);
    return t;
  }
}
