/*
 * Copyright 2020 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * 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 org.keycloak.testsuite.federation.ldap;

import java.util.ArrayList;
import java.util.List;

import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.core.Response;

import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.federation.kerberos.KerberosFederationProvider;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.storage.ldap.idm.model.LDAPObject;
import org.keycloak.testsuite.admin.ApiUtil;
import org.keycloak.testsuite.util.LDAPRule;
import org.keycloak.testsuite.util.LDAPTestUtils;
import org.keycloak.testsuite.util.UserBuilder;

import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.contains;
import static org.junit.Assert.assertEquals;

/**
 *
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class LDAPAdminRestApiTest extends AbstractLDAPTest {

    @ClassRule
    public static LDAPRule ldapRule = new LDAPRule();

    @Override
    protected LDAPRule getLDAPRule() {
        return ldapRule;
    }

    @Override
    protected void afterImportTestRealm() {
        testingClient.server().run(session -> {
            LDAPTestContext ctx = LDAPTestContext.init(session);
            RealmModel appRealm = ctx.getRealm();

            LDAPTestUtils.addLocalUser(session, appRealm, "marykeycloak", "mary@test.com", "password-app");

            LDAPTestUtils.addZipCodeLDAPMapper(appRealm, ctx.getLdapModel());

            // Delete all LDAP users and add some new for testing
            LDAPTestUtils.removeAllLDAPUsers(ctx.getLdapProvider(), appRealm);

            LDAPObject john = LDAPTestUtils.addLDAPUser(ctx.getLdapProvider(), appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234");
            LDAPTestUtils.updateLDAPPassword(ctx.getLdapProvider(), john, "Password1");
        });
    }

    @Test
    public void createUserWithAdminRest() throws Exception {
        // Create user just with the username
        UserRepresentation user1 = UserBuilder.create()
                .username("admintestuser1")
                .password("userpass")
                .enabled(true)
                .build();
        String newUserId1 = createUserExpectSuccess(user1);
        getCleanup().addUserId(newUserId1);

        // Create user with firstName and lastNAme
        UserRepresentation user2 = UserBuilder.create()
                .username("admintestuser2")
                .password("userpass")
                .email("admintestuser2@keycloak.org")
                .firstName("Some")
                .lastName("OtherUser")
                .enabled(true)
                .build();
        String newUserId2 = createUserExpectSuccess(user2);
        getCleanup().addUserId(newUserId2);

        // Create user with filled LDAP_ID should fail
        UserRepresentation user3 = UserBuilder.create()
                .username("admintestuser3")
                .password("userpass")
                .addAttribute(LDAPConstants.LDAP_ID, "123456")
                .enabled(true)
                .build();
        createUserExpectError(user3);

        // Create user with filled LDAP_ENTRY_DN should fail
        UserRepresentation user4 = UserBuilder.create()
                .username("admintestuser4")
                .password("userpass")
                .addAttribute(LDAPConstants.LDAP_ENTRY_DN, "ou=users,dc=foo")
                .enabled(true)
                .build();
        createUserExpectError(user4);
    }

    @Test
    public void updateUserWithAdminRest() throws Exception {
        UserResource userRes = ApiUtil.findUserByUsernameId(testRealm(), "johnkeycloak");
        UserRepresentation user = userRes.toRepresentation();

        List<String> origLdapId = new ArrayList<>(user.getAttributes().get(LDAPConstants.LDAP_ID));
        List<String> origLdapEntryDn = new ArrayList<>(user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN));
        Assert.assertEquals(1, origLdapId.size());
        Assert.assertEquals(1, origLdapEntryDn.size());
        Assert.assertThat(user.getAttributes().keySet(), not(contains(KerberosFederationProvider.KERBEROS_PRINCIPAL)));

        // Trying to add KERBEROS_PRINCIPAL should fail (Adding attribute, which was not yet present)
        user.setFirstName("JohnUpdated");
        user.setLastName("DoeUpdated");
        user.singleAttribute(KerberosFederationProvider.KERBEROS_PRINCIPAL, "foo");
        updateUserExpectError(userRes, user);

        // The same test, but consider case sensitivity
        user.getAttributes().remove(KerberosFederationProvider.KERBEROS_PRINCIPAL);
        user.singleAttribute("KERberos_principal", "foo");
        updateUserExpectError(userRes, user);

        // Trying to update LDAP_ID should fail (Updating existing attribute, which was present)
        user.getAttributes().remove("KERberos_principal");
        user.getAttributes().get(LDAPConstants.LDAP_ID).remove(0);
        user.getAttributes().get(LDAPConstants.LDAP_ID).add("123");
        updateUserExpectError(userRes, user);

        // Trying to delete LDAP_ID should fail (Removing attribute, which was present here already)
        user.getAttributes().get(LDAPConstants.LDAP_ID).remove(0);
        updateUserExpectError(userRes, user);

        user.getAttributes().remove(LDAPConstants.LDAP_ID);
        userRes.update(user);
        user = userRes.toRepresentation();
        assertEquals(origLdapId, user.getAttributes().get(LDAPConstants.LDAP_ID));

        // Trying to update LDAP_ENTRY_DN should fail
        user.getAttributes().put(LDAPConstants.LDAP_ID, origLdapId);
        user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN).remove(0);
        user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN).add("ou=foo,dc=bar");
        updateUserExpectError(userRes, user);

        // Update firstName and lastName should be fine
        user.getAttributes().put(LDAPConstants.LDAP_ENTRY_DN, origLdapEntryDn);
        userRes.update(user);

        user = userRes.toRepresentation();
        assertEquals("JohnUpdated", user.getFirstName());
        assertEquals("DoeUpdated", user.getLastName());
        assertEquals(origLdapId, user.getAttributes().get(LDAPConstants.LDAP_ID));
        assertEquals(origLdapEntryDn, user.getAttributes().get(LDAPConstants.LDAP_ENTRY_DN));

        // Revert
        user.setFirstName("John");
        user.setLastName("Doe");
        userRes.update(user);
    }


    private String createUserExpectSuccess(UserRepresentation user) {
        Response response = testRealm().users().create(user);
        String newUserId = ApiUtil.getCreatedId(response);
        response.close();

        UserRepresentation userRep = testRealm().users().get(newUserId).toRepresentation();
        userRep.getAttributes().containsKey(LDAPConstants.LDAP_ID);
        userRep.getAttributes().containsKey(LDAPConstants.LDAP_ENTRY_DN);
        return newUserId;
    }

    private void createUserExpectError(UserRepresentation user) {
        Response response = testRealm().users().create(user);
        Assert.assertEquals(400, response.getStatus());
        response.close();
    }

    private void updateUserExpectError(UserResource userRes, UserRepresentation user) {
        try {
            userRes.update(user);
            Assert.fail("Not expected to successfully update user");
        } catch (BadRequestException e) {
            // Expected
        }
    }
}
