/*
 * Copyright 1999-2021 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.alibaba.nacos.plugin.auth.impl.controller;

import com.alibaba.nacos.api.model.Page;
import com.alibaba.nacos.plugin.auth.impl.configuration.AuthConfigs;
import com.alibaba.nacos.common.model.RestResult;
import com.alibaba.nacos.core.context.RequestContextHolder;
import com.alibaba.nacos.plugin.auth.api.IdentityContext;
import com.alibaba.nacos.plugin.auth.exception.AccessException;
import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants;
import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes;
import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo;
import com.alibaba.nacos.plugin.auth.impl.persistence.User;
import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleService;
import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUser;
import com.alibaba.nacos.plugin.auth.impl.users.NacosUserService;
import com.alibaba.nacos.sys.env.EnvUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
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.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

@ExtendWith(MockitoExtension.class)
class UserControllerTest {
    
    @Mock
    private HttpServletRequest request;
    
    @Mock
    private HttpServletResponse response;
    
    @Mock
    private AuthConfigs authConfigs;
    
    @Mock
    private IAuthenticationManager authenticationManager;
    
    @Mock
    private TokenManagerDelegate tokenManagerDelegate;
    
    @Mock
    private NacosUserService userDetailsService;
    
    @Mock
    private NacosRoleService roleService;
    
    @InjectMocks
    private UserController userController;
    
    private NacosUser user;
    
    @BeforeEach
    void setUp() throws Exception {
        user = new NacosUser();
        user.setUserName("nacos");
        user.setGlobalAdmin(true);
        user.setToken("1234567890");
        
        MockEnvironment mockEnvironment = new MockEnvironment();
        mockEnvironment.setProperty(AuthConstants.TOKEN_SECRET_KEY, Base64.getEncoder().encodeToString(
                "SecretKey0123$567890$234567890123456789012345678901234567890123456789".getBytes(
                        StandardCharsets.UTF_8)));
        mockEnvironment.setProperty(AuthConstants.TOKEN_EXPIRE_SECONDS,
                AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS.toString());
        
        EnvUtil.setEnvironment(mockEnvironment);
        RequestContextHolder.getContext().getAuthContext().setIdentityContext(new IdentityContext());
    }
    
    @AfterEach
    public void tearDown() {
        RequestContextHolder.removeContext();
    }
    
    @Test
    void testLoginWithAuthedUser() throws AccessException, IOException {
        when(authenticationManager.authenticate(request)).thenReturn(user);
        when(authenticationManager.hasGlobalAdminRole(user)).thenReturn(true);
        when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name());
        when(tokenManagerDelegate.getTokenTtlInSeconds(anyString())).thenReturn(18000L);
        Object actual = userController.login("nacos", "nacos", response, request);
        assertTrue(actual instanceof JsonNode);
        String actualString = actual.toString();
        assertTrue(actualString.contains("\"accessToken\":\"1234567890\""));
        assertTrue(actualString.contains("\"tokenTtl\":18000"));
        assertTrue(actualString.contains("\"globalAdmin\":true"));
    }
    
    @Test
    void testCreateUser1() {
        when(userDetailsService.getUser("test")).thenReturn(null);
        RestResult<String> result = (RestResult<String>) userController.createUser("test", "test");
        assertEquals(200, result.getCode());
        
    }
    
    @Test
    void testCreateUser2() {
        when(userDetailsService.getUser("test")).thenReturn(new User());
        assertThrows(IllegalArgumentException.class, () -> {
            userController.createUser("test", "test");
        });
    }
    
    @Test
    void testCreateUserNamedNacos() {
        RestResult<String> result = (RestResult<String>) userController.createUser("nacos", "test");
        assertEquals(409, result.getCode());
    }
    
    @Test
    void testCreateAdminUser1() {
        when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name());
        when(authenticationManager.hasGlobalAdminRole()).thenReturn(true);
        
        RestResult<String> result = (RestResult<String>) userController.createAdminUser("test");
        
        assertEquals(HttpStatus.CONFLICT.value(), result.getCode());
    }
    
    @Test
    void testCreateAdminUser2() {
        RestResult<String> result = (RestResult<String>) userController.createAdminUser("test");
        
        assertEquals(HttpStatus.NOT_IMPLEMENTED.value(), result.getCode());
    }
    
    @Test
    void testCreateAdminUser3() {
        when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name());
        when(authenticationManager.hasGlobalAdminRole()).thenReturn(false);
        ObjectNode result = (ObjectNode) userController.createAdminUser("test");
        
        assertEquals("test", result.get(AuthConstants.PARAM_PASSWORD).asText());
    }
    
    @Test
    void testDeleteUser1() {
        List<RoleInfo> roleInfoList = new ArrayList<>(1);
        RoleInfo testRole = new RoleInfo();
        testRole.setUsername("nacos");
        testRole.setRole(AuthConstants.GLOBAL_ADMIN_ROLE);
        roleInfoList.add(testRole);
        
        when(roleService.getRoles(anyString())).thenReturn(roleInfoList);
        
        assertThrows(IllegalArgumentException.class, () -> {
            userController.deleteUser("nacos");
        });
        
    }
    
    @Test
    void testDeleteUser2() {
        List<RoleInfo> roleInfoList = new ArrayList<>(1);
        RoleInfo testRole = new RoleInfo();
        testRole.setUsername("nacos");
        testRole.setRole("testRole");
        roleInfoList.add(testRole);
        
        when(roleService.getRoles(anyString())).thenReturn(roleInfoList);
        
        RestResult<String> result = (RestResult<String>) userController.deleteUser("nacos");
        assertEquals(200, result.getCode());
    }
    
    @Test
    void testUpdateUser1() throws IOException {
        
        when(authConfigs.isAuthEnabled()).thenReturn(false);
        when(userDetailsService.getUser(anyString())).thenReturn(new User());
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        RestResult<String> result = (RestResult<String>) userController.updateUser("nacos", "test",
                mockHttpServletResponse, mockHttpServletRequest);
        assertEquals(200, result.getCode());
        
    }
    
    @Test
    void testUpdateUser2() {
        
        when(authConfigs.isAuthEnabled()).thenReturn(false);
        when(userDetailsService.getUser(anyString())).thenReturn(null);
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        
        assertThrows(IllegalArgumentException.class, () -> {
            userController.updateUser("nacos", "test", mockHttpServletResponse, mockHttpServletRequest);
        });
    }
    
    @Test
    void testUpdateUser3() throws IOException {
        RequestContextHolder.getContext().getAuthContext().setIdentityContext(null);
        when(authConfigs.isAuthEnabled()).thenReturn(true);
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        Object result = userController.updateUser("nacos", "test", mockHttpServletResponse, mockHttpServletRequest);
        
        assertNull(result);
        assertEquals(HttpServletResponse.SC_UNAUTHORIZED, mockHttpServletResponse.getStatus());
        
    }
    
    @Test
    void testUpdateUser4() throws IOException {
        RequestContextHolder.getContext().getAuthContext().getIdentityContext()
                .setParameter(AuthConstants.NACOS_USER_KEY, user);
        when(authConfigs.isAuthEnabled()).thenReturn(true);
        when(userDetailsService.getUser(anyString())).thenReturn(new User());
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        RestResult<String> result = (RestResult<String>) userController.updateUser("nacos", "test",
                mockHttpServletResponse, mockHttpServletRequest);
        assertEquals(200, result.getCode());
        
    }
    
    @Test
    void testUpdateUser5() throws IOException, AccessException {
        RequestContextHolder.getContext().getAuthContext().getIdentityContext()
                .setParameter(AuthConstants.NACOS_USER_KEY, null);
        when(authConfigs.isAuthEnabled()).thenReturn(true);
        when(userDetailsService.getUser(anyString())).thenReturn(new User());
        when(authenticationManager.authenticate(any(MockHttpServletRequest.class))).thenReturn(user);
        
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        RestResult<String> result = (RestResult<String>) userController.updateUser("nacos", "test",
                mockHttpServletResponse, mockHttpServletRequest);
        assertEquals(200, result.getCode());
        
    }
    
    @Test
    void testUpdateUser6() throws IOException, AccessException {
        RequestContextHolder.getContext().getAuthContext().getIdentityContext()
                .setParameter(AuthConstants.NACOS_USER_KEY, null);
        when(authConfigs.isAuthEnabled()).thenReturn(true);
        when(authenticationManager.authenticate(any(MockHttpServletRequest.class))).thenReturn(null);
        
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        Object result = userController.updateUser("nacos", "test", mockHttpServletResponse, mockHttpServletRequest);
        
        assertNull(result);
        assertEquals(HttpServletResponse.SC_UNAUTHORIZED, mockHttpServletResponse.getStatus());
        
    }
    
    @Test
    void testUpdateUser7() throws IOException, AccessException {
        RequestContextHolder.getContext().getAuthContext().getIdentityContext()
                .setParameter(AuthConstants.NACOS_USER_KEY, null);
        when(authConfigs.isAuthEnabled()).thenReturn(true);
        when(authenticationManager.authenticate(any(MockHttpServletRequest.class))).thenThrow(
                new AccessException("test"));
        
        MockHttpServletRequest mockHttpServletRequest = new MockHttpServletRequest();
        MockHttpServletResponse mockHttpServletResponse = new MockHttpServletResponse();
        Object result = userController.updateUser("nacos", "test", mockHttpServletResponse, mockHttpServletRequest);
        
        assertNull(result);
        assertEquals(HttpServletResponse.SC_FORBIDDEN, mockHttpServletResponse.getStatus());
        
    }
    
    @Test
    void testGetUsers() {
        Page<User> userPage = new Page<>();
        
        when(userDetailsService.getUsers(anyInt(), anyInt(), anyString())).thenReturn(userPage);
        
        Page<User> nacos = userController.getUsers(1, 10, "nacos");
        assertEquals(userPage, nacos);
    }
    
    @Test
    void testFuzzySearchUser() {
        Page<User> userPage = new Page<>();
        
        when(userDetailsService.findUsers(anyString(), anyInt(), anyInt())).thenReturn(userPage);
        
        Page<User> nacos = userController.fuzzySearchUser(1, 10, "nacos");
        assertEquals(userPage, nacos);
    }
    
    @Test
    void testSearchUsersLikeUsername() {
        List<String> test = new ArrayList<>(1);
        
        when(userDetailsService.findUserNames(anyString())).thenReturn(test);
        List<String> list = userController.searchUsersLikeUsername("nacos");
        
        assertEquals(test, list);
    }
    
}
