/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.cloudstack.api.command;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.nullable;

import java.lang.reflect.Field;
import java.net.InetAddress;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiErrorCode;
import org.apache.cloudstack.api.ApiServerService;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.ServerApiException;
import org.apache.cloudstack.api.auth.APIAuthenticationType;
import org.apache.cloudstack.api.response.LoginCmdResponse;
import org.apache.cloudstack.saml.SAML2AuthManager;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;

import com.cloud.domain.DomainVO;
import com.cloud.domain.dao.DomainDao;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.User;
import com.cloud.user.UserAccountVO;
import com.cloud.user.UserVO;
import com.cloud.user.dao.UserAccountDao;
import com.cloud.user.dao.UserDao;
import com.cloud.utils.HttpUtils;

import junit.framework.TestCase;

@RunWith(MockitoJUnitRunner.class)
public class ListAndSwitchSAMLAccountCmdTest extends TestCase {
    @Mock
    ApiServerService apiServer;

    @Mock
    SAML2AuthManager samlAuthManager;

    @Mock
    AccountService accountService;

    @Mock
    UserAccountDao userAccountDao;

    @Mock
    UserDao userDao;

    @Mock
    DomainDao domainDao;

    @Mock
    HttpSession session;

    @Mock
    HttpServletResponse resp;

    @Mock
    HttpServletRequest req;

    final String sessionId = "node0xxxxxxxxxxxxx";
    Cookie[] cookies;

    @Test
    public void testListAndSwitchSAMLAccountCmd() throws Exception {
        // Setup
        final Map<String, Object[]> params = new HashMap<String, Object[]>();
        final String sessionKeyValue = "someSessionIDValue";
        Mockito.when(session.getAttribute(ApiConstants.SESSIONKEY)).thenReturn(sessionKeyValue);
        Mockito.when(session.getAttribute("userid")).thenReturn(2L);
        Mockito.when(session.getId()).thenReturn(sessionId);
        params.put(ApiConstants.USER_ID, new String[]{"2"});
        params.put(ApiConstants.DOMAIN_ID, new String[]{"1"});
        Mockito.when(userDao.findByUuid(anyString())).thenReturn(new UserVO(2L));
        Mockito.when(domainDao.findByUuid(anyString())).thenReturn(new DomainVO());

        // Mock/field setup
        ListAndSwitchSAMLAccountCmd cmd = new ListAndSwitchSAMLAccountCmd();
        Field apiServerField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_apiServer");
        apiServerField.setAccessible(true);
        apiServerField.set(cmd, apiServer);

        Field managerField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_samlAuthManager");
        managerField.setAccessible(true);
        managerField.set(cmd, samlAuthManager);

        Field accountServiceField = BaseCmd.class.getDeclaredField("_accountService");
        accountServiceField.setAccessible(true);
        accountServiceField.set(cmd, accountService);

        Field userAccountDaoField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_userAccountDao");
        userAccountDaoField.setAccessible(true);
        userAccountDaoField.set(cmd, userAccountDao);

        Field userDaoField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_userDao");
        userDaoField.setAccessible(true);
        userDaoField.set(cmd, userDao);

        Field domainDaoField = ListAndSwitchSAMLAccountCmd.class.getDeclaredField("_domainDao");
        domainDaoField.setAccessible(true);
        domainDaoField.set(cmd, domainDao);

        // invalid session test
        try {
            cmd.authenticate("command", params, null, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
        } catch (ServerApiException exception) {
            assertEquals(exception.getErrorCode(), ApiErrorCode.UNAUTHORIZED);
        } finally {
            Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
        }

        // invalid sessionkey value test
        params.put(ApiConstants.SESSIONKEY, new String[]{"someOtherValue"});
        try {
            Mockito.when(session.isNew()).thenReturn(false);
            cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
        } catch (ServerApiException exception) {
            assertEquals(exception.getErrorCode(), ApiErrorCode.UNAUTHORIZED);
        } finally {
            Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
        }

        // valid sessionkey value and invalid JSESSIONID test
        cookies = new Cookie[2];
        cookies[0] = new Cookie(ApiConstants.SESSIONKEY, sessionKeyValue);
        cookies[1] = new Cookie("JSESSIONID", "invalid-JSESSIONID");
        Mockito.when(req.getCookies()).thenReturn(cookies);
        params.put(ApiConstants.SESSIONKEY, new String[]{sessionKeyValue});
        try {
            cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
        } catch (ServerApiException exception) {
            assertEquals(exception.getErrorCode(), ApiErrorCode.UNAUTHORIZED);
        } finally {
            Mockito.verify(accountService, Mockito.times(0)).getUserAccountById(Mockito.anyLong());
        }

        // valid sessionkey value and valid JSESSIONID test
        cookies = new Cookie[2];
        cookies[0] = new Cookie(ApiConstants.SESSIONKEY, sessionKeyValue);
        cookies[1] = new Cookie("JSESSIONID", sessionId + ".node0");
        Mockito.when(req.getCookies()).thenReturn(cookies);
        params.put(ApiConstants.SESSIONKEY, new String[]{sessionKeyValue});
        try {
            cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
        } catch (ServerApiException exception) {
            assertEquals(exception.getErrorCode(), ApiErrorCode.ACCOUNT_ERROR);
        } finally {
            Mockito.verify(accountService, Mockito.times(1)).getUserAccountById(Mockito.anyLong());
        }

        // valid sessionkey, invalid useraccount type (non-saml) value test
        UserAccountVO mockedUserAccount = new UserAccountVO();
        mockedUserAccount.setId(2L);
        mockedUserAccount.setAccountState(Account.State.ENABLED.toString());
        mockedUserAccount.setUsername("someUsername");
        mockedUserAccount.setExternalEntity("some IDP ID");
        mockedUserAccount.setDomainId(0L);
        mockedUserAccount.setSource(User.Source.UNKNOWN);
        Mockito.when(accountService.getUserAccountById(Mockito.anyLong())).thenReturn(mockedUserAccount);
        try {
            cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
        } catch (ServerApiException exception) {
            assertEquals(exception.getErrorCode(), ApiErrorCode.ACCOUNT_ERROR);
        } finally {
            // accountService should have been called twice by now, for this case and the case above
            Mockito.verify(accountService, Mockito.times(2)).getUserAccountById(Mockito.anyLong());
        }

        // all valid test
        mockedUserAccount.setSource(User.Source.SAML2);
        Mockito.when(accountService.getUserAccountById(Mockito.anyLong())).thenReturn(mockedUserAccount);
        Mockito.when(apiServer.verifyUser(Mockito.anyLong())).thenReturn(true);
        LoginCmdResponse loginCmdResponse = new LoginCmdResponse();
        loginCmdResponse.setUserId("1");
        loginCmdResponse.setDomainId("1");
        loginCmdResponse.setType("1");
        loginCmdResponse.setUsername("userName");
        loginCmdResponse.setAccount("someAccount");
        loginCmdResponse.setFirstName("firstName");
        loginCmdResponse.setLastName("lastName");
        loginCmdResponse.setSessionKey("newSessionKeyString");
        loginCmdResponse.set2FAenabled("false");
        Mockito.when(apiServer.loginUser(nullable(HttpSession.class), nullable(String.class), nullable(String.class),
                nullable(Long.class), nullable(String.class), nullable(InetAddress.class), nullable(Map.class))).thenReturn(loginCmdResponse);
        try {
            cmd.authenticate("command", params, session, null, HttpUtils.RESPONSE_TYPE_JSON, new StringBuilder(), req, resp);
        } catch (ServerApiException exception) {
            fail("SAML list and switch account API failed to pass for all valid data: " + exception.getMessage());
        } finally {
            // accountService should have been called 4 times by now, for this case twice and 2 for cases above
            Mockito.verify(accountService, Mockito.times(4)).getUserAccountById(Mockito.anyLong());
        }
    }

    @Test
    public void testGetAPIType() {
        Assert.assertTrue(new ListAndSwitchSAMLAccountCmd().getAPIType() == APIAuthenticationType.READONLY_API);
    }

}
