package io.aiven.klaw.service;

import io.aiven.klaw.config.ManageDatabase;
import io.aiven.klaw.constants.TestConstants;
import io.aiven.klaw.constants.UriConstants;
import io.aiven.klaw.dao.RegisterUserInfo;
import io.aiven.klaw.dao.UserInfo;
import io.aiven.klaw.helpers.KwConstants;
import io.aiven.klaw.helpers.db.rdbms.HandleDbRequestsJdbc;
import io.aiven.klaw.model.enums.ApiResultStatus;
import io.aiven.klaw.model.enums.RolesType;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.catalina.connector.Response;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.test.util.ReflectionTestUtils;

@ExtendWith(MockitoExtension.class)
class UiControllerLoginServiceTest {
  @Mock HandleDbRequestsJdbc handleDbRequestsJdbc;
  @Mock private ManageDatabase manageDatabase;
  @Mock private CommonUtilsService commonUtilsService;
  @InjectMocks @Spy private UiControllerLoginService uiControllerLoginService;
  @Mock private DefaultOAuth2User defaultOAuth2User;
  @Mock private UserDetails userDetails;
  @Mock Authentication authentication;

  @BeforeEach
  public void setUp() {
    ReflectionTestUtils.setField(uiControllerLoginService, "authenticationType", "db");
    ReflectionTestUtils.setField(uiControllerLoginService, "enableUserAuthorizationFromAD", false);
    ReflectionTestUtils.setField(uiControllerLoginService, "nameAttribute", "name");
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", "false");
    ReflectionTestUtils.setField(uiControllerLoginService, "kwInstallationType", "onpremise");
  }

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

  @Test
  public void getReturningPage_UserNameNull() {
    Assertions.assertEquals(
        UriConstants.DEFAULT_PAGE, uiControllerLoginService.getReturningPage("", null));
  }

  @Test
  public void getReturningPage_UserNameNotNull_UserInfoNotNull() {
    UserInfo userInfo = new UserInfo();

    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(userInfo);

    Assertions.assertEquals(
        "", uiControllerLoginService.getReturningPage("", TestConstants.USERNAME));
  }

  @ParameterizedTest
  @ValueSource(
      strings = {
        UriConstants.LOGIN_PAGE,
        UriConstants.HOME_PAGE,
        UriConstants.REGISTER_PAGE,
        UriConstants.FORGOT_PASSWORD_PAGE,
        UriConstants.NEW_AD_USER_PAGE,
        UriConstants.REGISTRATION_REVIEW
      })
  public void getReturningPage_UserNameNotNull_UserInfoNotNull_IndexPage(String uri) {
    UserInfo userInfo = new UserInfo();

    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(userInfo);

    Assertions.assertEquals(
        UriConstants.INDEX_PAGE,
        uiControllerLoginService.getReturningPage(uri, TestConstants.USERNAME));
  }

  @Test
  public void getReturningPage_UserNameNotNull_UserInfoNull_ActiveDirectory() {
    ReflectionTestUtils.setField(uiControllerLoginService, "authenticationType", "ad");

    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);

    Assertions.assertEquals(
        UriConstants.REGISTER_LDAP_PAGE,
        uiControllerLoginService.getReturningPage("", TestConstants.USERNAME));
  }

  @Test
  public void getReturningPage_UserNameNotNull_UserInfoNull() {
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);

    Assertions.assertEquals(
        UriConstants.REGISTER_PAGE,
        uiControllerLoginService.getReturningPage("", TestConstants.USERNAME));
  }

  @ParameterizedTest
  @ValueSource(
      strings = {
        UriConstants.LOGIN_PAGE,
        UriConstants.REGISTER_PAGE,
        UriConstants.REGISTER_LDAP_PAGE,
        UriConstants.FORGOT_PASSWORD_PAGE,
        UriConstants.NEW_AD_USER_PAGE,
        UriConstants.TERMS_PAGE,
        UriConstants.FEEDBACK_PAGE,
        UriConstants.REGISTRATION_REVIEW
      })
  public void getReturningPage_Failure(String uri) {
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME))
        .thenThrow(new RuntimeException("Error Occurred"));

    Assertions.assertEquals(
        uri, uiControllerLoginService.getReturningPage(uri, TestConstants.USERNAME));
  }

  @Test
  public void getReturningPage_Failure() {
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME))
        .thenThrow(new RuntimeException("Error Occurred"));

    Assertions.assertEquals(
        UriConstants.DEFAULT_PAGE,
        uiControllerLoginService.getReturningPage("", TestConstants.USERNAME));
  }

  @Test
  public void checkAnonymousLogin_WithOAuth2AndEmailClaim() {
    ReflectionTestUtils.setField(uiControllerLoginService, "enableUserAuthorizationFromAD", false);
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", "true");
    AbstractAuthenticationToken authenticationToken = Mockito.mock(OAuth2AuthenticationToken.class);
    Mockito.when(authenticationToken.getPrincipal()).thenReturn(defaultOAuth2User);
    Mockito.when(defaultOAuth2User.getAttributes())
        .thenReturn(
            Map.of(
                "name", TestConstants.USERNAME, "email", TestConstants.USERNAME + "@example.com"));
    Mockito.when(
            manageDatabase.getTeamIdFromTeamName(
                KwConstants.DEFAULT_TENANT_ID, KwConstants.STAGINGTEAM))
        .thenReturn(TestConstants.TEAM_ID);
    Mockito.when(handleDbRequestsJdbc.registerUserForAD(ArgumentMatchers.any()))
        .thenReturn(ApiResultStatus.SUCCESS.value);

    HttpServletResponse response = new Response();

    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    SecurityContextHolder.setContext(securityContext);
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);

    String actual =
        uiControllerLoginService.checkAnonymousLogin(
            "", authenticationToken, response, TestConstants.USERNAME);

    ArgumentCaptor<RegisterUserInfo> userInfoCaptor =
        ArgumentCaptor.forClass(RegisterUserInfo.class);
    Mockito.verify(handleDbRequestsJdbc).registerUserForAD(userInfoCaptor.capture());
    Assertions.assertEquals(
        TestConstants.USERNAME + "@example.com", userInfoCaptor.getValue().getMailid());
  }

  @Test
  public void checkAnonymousLogin_WithOAuth2AndNoEmailClaim() {
    ReflectionTestUtils.setField(uiControllerLoginService, "enableUserAuthorizationFromAD", false);
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", "true");
    AbstractAuthenticationToken authenticationToken = Mockito.mock(OAuth2AuthenticationToken.class);
    Mockito.when(authenticationToken.getPrincipal()).thenReturn(defaultOAuth2User);
    Mockito.when(defaultOAuth2User.getAttributes())
        .thenReturn(Map.of("name", TestConstants.USERNAME));
    Mockito.when(
            manageDatabase.getTeamIdFromTeamName(
                KwConstants.DEFAULT_TENANT_ID, KwConstants.STAGINGTEAM))
        .thenReturn(TestConstants.TEAM_ID);
    Mockito.when(handleDbRequestsJdbc.registerUserForAD(ArgumentMatchers.any()))
        .thenReturn(ApiResultStatus.SUCCESS.value);

    HttpServletResponse response = new Response();

    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    SecurityContextHolder.setContext(securityContext);
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);

    String actual =
        uiControllerLoginService.checkAnonymousLogin(
            "", authenticationToken, response, TestConstants.USERNAME);

    ArgumentCaptor<RegisterUserInfo> userInfoCaptor =
        ArgumentCaptor.forClass(RegisterUserInfo.class);
    Mockito.verify(handleDbRequestsJdbc).registerUserForAD(userInfoCaptor.capture());
    Assertions.assertNull(userInfoCaptor.getValue().getMailid());
  }

  @Test
  public void checkAnonymousLogin_UserAuthorizationFromAdDisabled() {
    AbstractAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(
            TestConstants.USERNAME,
            TestConstants.PASSWORD,
            List.of(new SimpleGrantedAuthority("role")));
    HttpServletResponse response = new Response();

    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);
    Mockito.when(handleDbRequestsJdbc.getRegistrationId(TestConstants.USERNAME)).thenReturn(null);
    Mockito.when(
            manageDatabase.getTeamIdFromTeamName(
                KwConstants.DEFAULT_TENANT_ID, KwConstants.STAGINGTEAM))
        .thenReturn(TestConstants.TEAM_ID);
    Mockito.when(handleDbRequestsJdbc.registerUserForAD(ArgumentMatchers.any()))
        .thenReturn(ApiResultStatus.SUCCESS.value);

    String actual =
        uiControllerLoginService.checkAnonymousLogin(
            "", authenticationToken, response, TestConstants.USERNAME);

    Assertions.assertTrue(
        actual.contains(UriConstants.REDIRECT + UriConstants.REGISTER_USER_REGISTRATION_ID));
  }

  @Test
  public void checkAnonymousLogin_UserAuthorizationFromAdEnabled() {
    ReflectionTestUtils.setField(uiControllerLoginService, "enableUserAuthorizationFromAD", true);
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", "true");
    AbstractAuthenticationToken authenticationToken = Mockito.mock(OAuth2AuthenticationToken.class);
    HttpServletResponse response = new Response();

    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    SecurityContextHolder.setContext(securityContext);
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);
    Mockito.when(manageDatabase.getRolesPermissionsPerTenant(KwConstants.DEFAULT_TENANT_ID))
        .thenReturn(Map.of(TestConstants.ROLE, Set.of(TestConstants.PERMISSION)));
    Mockito.when(handleDbRequestsJdbc.getRegistrationId(TestConstants.USERNAME)).thenReturn(null);
    Mockito.when(
            manageDatabase.getTeamIdFromTeamName(
                KwConstants.DEFAULT_TENANT_ID, KwConstants.STAGINGTEAM))
        .thenReturn(TestConstants.TEAM_ID);
    Mockito.when(handleDbRequestsJdbc.registerUserForAD(ArgumentMatchers.any()))
        .thenReturn(ApiResultStatus.SUCCESS.value);

    String actual =
        uiControllerLoginService.checkAnonymousLogin(
            "", authenticationToken, response, TestConstants.USERNAME);

    Assertions.assertTrue(
        actual.contains(UriConstants.REDIRECT + UriConstants.REGISTER_USER_REGISTRATION_ID));
  }

  @Test
  public void checkAnonymousLogin_UserAuthorizationFromAdEnabled_MultipleRoleMatched() {
    ReflectionTestUtils.setField(uiControllerLoginService, "enableUserAuthorizationFromAD", true);
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", "true");
    AbstractAuthenticationToken authenticationToken = Mockito.mock(OAuth2AuthenticationToken.class);
    HttpServletResponse response = Mockito.mock(HttpServletResponse.class);

    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    SecurityContextHolder.setContext(securityContext);

    Mockito.when(authenticationToken.getPrincipal()).thenReturn(defaultOAuth2User);
    Mockito.when(defaultOAuth2User.getAttributes())
        .thenReturn(Map.of("name", TestConstants.USERNAME));
    Mockito.when(defaultOAuth2User.getAuthorities())
        .thenReturn(
            (Collection)
                List.of(
                    new SimpleGrantedAuthority(TestConstants.ROLE + "_" + TestConstants.ROLE),
                    new SimpleGrantedAuthority(TestConstants.ROLE + "_" + TestConstants.ROLE)));
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);
    Mockito.when(manageDatabase.getRolesPermissionsPerTenant(KwConstants.DEFAULT_TENANT_ID))
        .thenReturn(Map.of(TestConstants.ROLE, Set.of(TestConstants.PERMISSION)));

    String actual =
        uiControllerLoginService.checkAnonymousLogin(
            "", authenticationToken, response, TestConstants.USERNAME);

    Assertions.assertEquals(actual, UriConstants.OAUTH_LOGIN);
  }

  @Test
  public void checkAnonymousLogin_UserAuthorizationFromAdEnabled_NoRoleMatched() {
    ReflectionTestUtils.setField(uiControllerLoginService, "enableUserAuthorizationFromAD", true);
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", "true");
    AbstractAuthenticationToken authenticationToken = Mockito.mock(OAuth2AuthenticationToken.class);
    HttpServletResponse response = Mockito.mock(HttpServletResponse.class);

    SecurityContext securityContext = Mockito.mock(SecurityContext.class);
    SecurityContextHolder.setContext(securityContext);

    Mockito.when(authenticationToken.getPrincipal()).thenReturn(defaultOAuth2User);
    Mockito.when(defaultOAuth2User.getAttributes())
        .thenReturn(Map.of("name", TestConstants.USERNAME));
    Mockito.when(defaultOAuth2User.getAuthorities()).thenReturn(List.of());
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(null);
    Mockito.when(manageDatabase.getRolesPermissionsPerTenant(KwConstants.DEFAULT_TENANT_ID))
        .thenReturn(Map.of(TestConstants.ROLE, Set.of(TestConstants.PERMISSION)));

    String actual =
        uiControllerLoginService.checkAnonymousLogin(
            "", authenticationToken, response, TestConstants.USERNAME);

    Assertions.assertEquals(actual, UriConstants.OAUTH_LOGIN);
  }

  @Test
  public void checkAuth_TenantsPage() {
    AbstractAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(
            TestConstants.USERNAME,
            TestConstants.PASSWORD,
            List.of(new SimpleGrantedAuthority("role")));
    HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
    HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
    UserInfo userInfo = new UserInfo();
    userInfo.setRole(RolesType.SUPERADMIN.name());

    loginMock();
    Mockito.when(authentication.getPrincipal()).thenReturn(userDetails);
    Mockito.when(userDetails.getUsername()).thenReturn(TestConstants.USERNAME);
    Mockito.when(manageDatabase.getHandleDbRequests()).thenReturn(handleDbRequestsJdbc);
    Mockito.when(handleDbRequestsJdbc.getUsersInfo(TestConstants.USERNAME)).thenReturn(userInfo);
    Mockito.when(commonUtilsService.getTenantId(TestConstants.USERNAME))
        .thenReturn(KwConstants.DEFAULT_TENANT_ID);
    Mockito.doReturn(UriConstants.TENANTS_PAGE)
        .when(uiControllerLoginService)
        .getReturningPage(UriConstants.TENANTS_PAGE, TestConstants.USERNAME);

    String actual =
        uiControllerLoginService.checkAuth(
            UriConstants.TENANTS_PAGE, request, response, authenticationToken);
    Assertions.assertEquals(UriConstants.TENANTS_PAGE, actual);
  }

  @ParameterizedTest
  @CsvSource({
    UriConstants.REGISTER + ", true",
    UriConstants.REGISTRATION_REVIEW_PAGE + ", true",
    UriConstants.FORGOT_PASSWORD + ", true",
    UriConstants.FORGOT_PASSWORD_PAGE + ", true",
    UriConstants.REGISTER + ", false",
    UriConstants.REGISTRATION_REVIEW_PAGE + ", false",
    UriConstants.FORGOT_PASSWORD + ", false",
    UriConstants.FORGOT_PASSWORD_PAGE + ", false"
  })
  public void checkAuth(String url, String ssoEnabled) {
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", ssoEnabled);
    AbstractAuthenticationToken authenticationToken =
        new UsernamePasswordAuthenticationToken(
            TestConstants.USERNAME,
            TestConstants.PASSWORD,
            List.of(new SimpleGrantedAuthority("role")));
    HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
    HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
    UserInfo userInfo = new UserInfo();
    userInfo.setRole(RolesType.SUPERADMIN.name());

    loginMock();
    Mockito.when(authentication.getPrincipal()).thenReturn(userDetails);
    Mockito.when(userDetails.getUsername()).thenReturn(TestConstants.USERNAME);

    String actual = uiControllerLoginService.checkAuth(url, request, response, authenticationToken);
    Assertions.assertEquals(url, actual);
  }

  @ParameterizedTest
  @ValueSource(strings = {"true", "false"})
  public void checkAuth_OAuth2AuthenticationToken(String ssoEnabled) {
    ReflectionTestUtils.setField(uiControllerLoginService, "ssoEnabled", ssoEnabled);
    ReflectionTestUtils.setField(uiControllerLoginService, "authenticationType", "ad");
    AbstractAuthenticationToken authenticationToken = Mockito.mock(OAuth2AuthenticationToken.class);
    HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
    HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
    UserInfo userInfo = new UserInfo();
    userInfo.setRole(RolesType.SUPERADMIN.name());

    loginMock();
    Mockito.when(authentication.getPrincipal()).thenReturn(defaultOAuth2User);
    Mockito.doReturn("")
        .when(uiControllerLoginService)
        .checkAnonymousLogin("", authenticationToken, response, null);
    String actual = uiControllerLoginService.checkAuth("", request, response, authenticationToken);
    Assertions.assertEquals("", actual);
  }
}
