package org.keycloak.testsuite.cli.admin;

import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.client.admin.cli.config.FileConfigHandler;
import org.keycloak.testsuite.cli.KcAdmExec;
import org.keycloak.testsuite.util.TempFileResource;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude;
import org.keycloak.testsuite.arquillian.annotation.AuthServerContainerExclude.AuthServer;
import static org.keycloak.testsuite.cli.KcAdmExec.execute;

/**
 * @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
 */
@AuthServerContainerExclude(AuthServer.REMOTE)
public class KcAdmSessionTest extends AbstractAdmCliTest {

    static Class<? extends List<ObjectNode>> LIST_OF_JSON = new ArrayList<ObjectNode>() {}.getClass();

    @Test
    public void test() throws IOException {

        FileConfigHandler handler = initCustomConfigFile();

        try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {

            // login as admin
            loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");

            // create realm
            KcAdmExec exe = execute("create realms --config '" + configFile.getName() + "' -s realm=demorealm -s enabled=true");

            assertExitCodeAndStreamSizes(exe, 0, 0, 1);
            Assert.assertTrue(exe.stderrLines().get(0).startsWith("Created "));

            // create user
            exe = execute("create users --config '" + configFile.getName() + "' -r demorealm -s username=testuser -s enabled=true -i");

            assertExitCodeAndStreamSizes(exe, 0, 1, 0);
            String userId = exe.stdoutLines().get(0);

            // add realm admin capabilities to user
            exe = execute("add-roles --config '" + configFile.getName() + "' -r demorealm --uusername testuser --cclientid realm-management --rolename realm-admin");

            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            // set password for the user
            exe = execute("set-password --config '" + configFile.getName() + "' -r demorealm --username testuser -p password");

            assertExitCodeAndStdErrSize(exe, 0, 0);


            // login as testuser
            loginAsUser(configFile.getFile(), serverUrl, "demorealm", "testuser", "password");


            // get realm roles
            exe = execute("get-roles --config '" + configFile.getName() + "'");

            assertExitCodeAndStdErrSize(exe, 0, 0);
            List<ObjectNode> roles = loadJson(exe.stdout(), LIST_OF_JSON);
            Assert.assertTrue("expect two realm roles available", roles.size() == 2);

            // create realm role
            exe = execute("create roles --config '" + configFile.getName() + "' -s name=testrole -s 'description=Test role' -o");

            assertExitCodeAndStdErrSize(exe, 0, 0);
            ObjectNode role = loadJson(exe.stdout(), ObjectNode.class);
            Assert.assertEquals("testrole", role.get("name").asText());
            String roleId = role.get("id").asText();

            // get realm roles again
            exe = execute("get-roles --config '" + configFile.getName() + "'");

            assertExitCodeAndStdErrSize(exe, 0, 0);
            roles = loadJson(exe.stdout(), LIST_OF_JSON);
            Assert.assertTrue("expect three realm roles available", roles.size() == 3);

            // create client
            exe = execute("create clients --config '" + configFile.getName() + "' -s clientId=testclient -i");

            assertExitCodeAndStreamSizes(exe, 0, 1, 0);
            String idOfClient = exe.stdoutLines().get(0);


            // create client role
            exe = execute("create clients/" + idOfClient + "/roles --config '" + configFile.getName() + "' -s name=clientrole  -s 'description=Test client role'");

            assertExitCodeAndStreamSizes(exe, 0, 0, 1);
            Assert.assertTrue(exe.stderrLines().get(0).startsWith("Created "));

            // make sure client role has been created
            exe = execute("get-roles --config '" + configFile.getName() + "' --cclientid testclient");

            assertExitCodeAndStdErrSize(exe, 0, 0);
            roles = loadJson(exe.stdout(), LIST_OF_JSON);
            Assert.assertTrue("expect one role", roles.size() == 1);
            Assert.assertEquals("clientrole", roles.get(0).get("name").asText());

            // add created role to user - we are realm admin so we can add role to ourself
            exe = execute("add-roles --config '" + configFile.getName() + "' --uusername testuser --cclientid testclient --rolename clientrole");

            assertExitCodeAndStreamSizes(exe, 0, 0, 0);


            // make sure the roles have been added
            exe = execute("get-roles --config '" + configFile.getName() + "' --uusername testuser --all");

            assertExitCodeAndStdErrSize(exe, 0, 0);
            ObjectNode node = loadJson(exe.stdout(), ObjectNode.class);
            Assert.assertNotNull(node.get("realmMappings"));

            List<String> realmMappings = StreamSupport.stream(node.get("realmMappings").spliterator(), false)
                    .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());
            Assert.assertEquals(Arrays.asList("offline_access", "uma_authorization"), realmMappings);

            ObjectNode clientRoles = (ObjectNode) node.get("clientMappings");
            //List<String> fields = asSortedList(clientRoles.fieldNames());
            List<String> fields = StreamSupport.stream(clientRoles.spliterator(), false)
                    .map(o -> o.get("client").asText()).sorted().collect(Collectors.toList());
            Assert.assertEquals(Arrays.asList("account", "realm-management", "testclient"), fields);

            realmMappings = StreamSupport.stream(clientRoles.get("account").get("mappings").spliterator(), false)
                    .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());
            Assert.assertEquals(Arrays.asList("manage-account", "view-profile"), realmMappings);

            realmMappings = StreamSupport.stream(clientRoles.get("realm-management").get("mappings").spliterator(), false)
                    .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());
            Assert.assertEquals(Arrays.asList("realm-admin"), realmMappings);

            realmMappings = StreamSupport.stream(clientRoles.get("testclient").get("mappings").spliterator(), false)
                    .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());
            Assert.assertEquals(Arrays.asList("clientrole"), realmMappings);



            // add a realm role to the user
            exe = execute("add-roles --config '" + configFile.getName() + "' --uusername testuser --rolename testrole");

            assertExitCodeAndStreamSizes(exe, 0, 0, 0);


            // get all roles for the user again
            exe = execute("get-roles --config '" + configFile.getName() + "' --uusername testuser --all");
            assertExitCodeAndStdErrSize(exe, 0, 0);

            node = loadJson(exe.stdout(), ObjectNode.class);
            Assert.assertNotNull(node.get("realmMappings"));

            realmMappings = StreamSupport.stream(node.get("realmMappings").spliterator(), false)
                    .map(o -> o.get("name").asText()).sorted().collect(Collectors.toList());
            Assert.assertEquals(Arrays.asList("offline_access", "testrole", "uma_authorization"), realmMappings);

            // create a group
            exe = execute("create groups --config '" + configFile.getName() + "' -s name=TestUsers -i");
            assertExitCodeAndStdErrSize(exe, 0, 0);
            String groupId = exe.stdoutLines().get(0);

            // create a sub-group
            exe = execute("create groups/" + groupId + "/children --config '" + configFile.getName() + "' -s name=TestPowerUsers -i");
            assertExitCodeAndStdErrSize(exe, 0, 0);
            String subGroupId = exe.stdoutLines().get(0);

            // add testuser to TestPowerUsers
            exe = execute("update users/" + userId + "/groups/" + subGroupId + " --config '" + configFile.getName()
                    + "' -s realm=demorealm -s userId=" + userId +  " -s groupId=" + subGroupId + " -n");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            // delete everything
            exe = execute("delete groups/" + subGroupId + " --config '" + configFile.getName() + "'");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            exe = execute("delete groups/" + groupId + " --config '" + configFile.getName() + "'");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            exe = execute("delete clients/" + idOfClient + " --config '" + configFile.getName() + "'");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            exe = execute("delete roles/testrole --config '" + configFile.getName() + "'");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            exe = execute("delete users/" + userId + " --config '" + configFile.getName() + "'");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);

            // delete realm as well - using initial master realm session still saved in config file
            exe = execute("delete realms/demorealm --config '" + configFile.getName() + "' --realm master");
            assertExitCodeAndStreamSizes(exe, 0, 0, 0);
        }
    }
}
