/*
 *
 *  * Copyright 2021  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.userprofile;

import java.util.regex.Pattern;

import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.messages.Messages;
import org.keycloak.userprofile.validation.StaticValidators;
import org.keycloak.userprofile.validation.UserProfileValidationResult;
import org.keycloak.userprofile.validation.ValidationChainBuilder;

/**
 * @author <a href="mailto:markus.till@bosch.io">Markus Till</a>
 */
public class LegacyUserProfileProvider implements UserProfileProvider {

    private final KeycloakSession session;
    private final Pattern readOnlyAttributes;
    private final Pattern adminReadOnlyAttributes;

    public LegacyUserProfileProvider(KeycloakSession session, Pattern readOnlyAttributes, Pattern adminReadOnlyAttributes) {
        this.session = session;
        this.readOnlyAttributes = readOnlyAttributes;
        this.adminReadOnlyAttributes = adminReadOnlyAttributes;
    }

    @Override
    public void close() {

    }

    @Override
    public UserProfileValidationResult validate(UserProfileContext updateContext, UserProfile updatedProfile) {
        RealmModel realm = this.session.getContext().getRealm();

        ValidationChainBuilder builder = ValidationChainBuilder.builder();
        switch (updateContext.getUpdateEvent()) {
            case UserResource:
                addReadOnlyAttributeValidators(builder, adminReadOnlyAttributes, updateContext, updatedProfile);
                break;
            case IdpReview:
                addBasicValidators(builder, !realm.isRegistrationEmailAsUsername());
                addReadOnlyAttributeValidators(builder, readOnlyAttributes, updateContext, updatedProfile);
                break;
            case Account:
            case RegistrationProfile:
            case UpdateProfile:
                addBasicValidators(builder, !realm.isRegistrationEmailAsUsername() && realm.isEditUsernameAllowed());
                addReadOnlyAttributeValidators(builder, readOnlyAttributes, updateContext, updatedProfile);
                addSessionValidators(builder);
                break;
            case RegistrationUserCreation:
                addUserCreationValidators(builder);
                addReadOnlyAttributeValidators(builder, readOnlyAttributes, updateContext, updatedProfile);
                break;
        }
        return new UserProfileValidationResult(builder.build().validate(updateContext,updatedProfile), updatedProfile);
    }

    @Override
    public boolean isReadOnlyAttribute(String key) {
        return readOnlyAttributes.matcher(key).find() || adminReadOnlyAttributes.matcher(key).find();
    }

    private void addUserCreationValidators(ValidationChainBuilder builder) {
        RealmModel realm = this.session.getContext().getRealm();

        if (realm.isRegistrationEmailAsUsername()) {
            builder.addAttributeValidator().forAttribute(UserModel.EMAIL)
                    .addSingleAttributeValueValidationFunction(Messages.INVALID_EMAIL, StaticValidators.isEmailValid())
                    .addSingleAttributeValueValidationFunction(Messages.MISSING_EMAIL, StaticValidators.isBlank())
                    .addSingleAttributeValueValidationFunction(Messages.EMAIL_EXISTS, StaticValidators.doesEmailExist(session)).build()
                    .build();


        } else {
            builder.addAttributeValidator().forAttribute(UserModel.USERNAME)
                    .addSingleAttributeValueValidationFunction(Messages.MISSING_USERNAME, StaticValidators.isBlank())
                    .addSingleAttributeValueValidationFunction(Messages.USERNAME_EXISTS,
                            (value, o) -> session.users().getUserByUsername(value, realm) == null)
                    .build();
        }
    }

    private void addBasicValidators(ValidationChainBuilder builder, boolean userNameExistsCondition) {

        builder.addAttributeValidator().forAttribute(UserModel.USERNAME)
                .addSingleAttributeValueValidationFunction(Messages.MISSING_USERNAME, StaticValidators.checkUsernameExists(userNameExistsCondition)).build()

                .addAttributeValidator().forAttribute(UserModel.FIRST_NAME)
                .addSingleAttributeValueValidationFunction(Messages.MISSING_FIRST_NAME, StaticValidators.isBlank()).build()

                .addAttributeValidator().forAttribute(UserModel.LAST_NAME)
                .addSingleAttributeValueValidationFunction(Messages.MISSING_LAST_NAME, StaticValidators.isBlank()).build()

                .addAttributeValidator().forAttribute(UserModel.EMAIL)
                .addSingleAttributeValueValidationFunction(Messages.MISSING_EMAIL, StaticValidators.isBlank())
                .addSingleAttributeValueValidationFunction(Messages.INVALID_EMAIL, StaticValidators.isEmailValid())
                .build();
    }

    private void addSessionValidators(ValidationChainBuilder builder) {
        RealmModel realm = this.session.getContext().getRealm();
        builder.addAttributeValidator().forAttribute(UserModel.USERNAME)
                .addSingleAttributeValueValidationFunction(Messages.USERNAME_EXISTS, StaticValidators.userNameExists(session))
                .addSingleAttributeValueValidationFunction(Messages.READ_ONLY_USERNAME, StaticValidators.isUserMutable(realm)).build()

                .addAttributeValidator().forAttribute(UserModel.EMAIL)
                .addSingleAttributeValueValidationFunction(Messages.EMAIL_EXISTS, StaticValidators.isEmailDuplicated(session))
                .addSingleAttributeValueValidationFunction(Messages.USERNAME_EXISTS, StaticValidators.doesEmailExistAsUsername(session)).build()
                .build();
    }

    private void addReadOnlyAttributeValidators(ValidationChainBuilder builder, Pattern configuredReadOnlyAttrs, UserProfileContext updateContext, UserProfile updatedProfile) {
        addValidatorsForReadOnlyAttributes(builder, configuredReadOnlyAttrs, updatedProfile);
        addValidatorsForReadOnlyAttributes(builder, configuredReadOnlyAttrs, updateContext.getCurrentProfile());
    }


    private void addValidatorsForReadOnlyAttributes(ValidationChainBuilder builder, Pattern configuredReadOnlyAttrsPattern, UserProfile profile) {
        if (profile == null) {
            return;
        }

        profile.getAttributes().keySet().stream()
                .filter(currentAttrName -> configuredReadOnlyAttrsPattern.matcher(currentAttrName).find())
                .forEach((currentAttrName) ->
                        builder.addAttributeValidator().forAttribute(currentAttrName)
                                .addValidationFunction(Messages.UPDATE_READ_ONLY_ATTRIBUTES_REJECTED, StaticValidators.isReadOnlyAttributeUnchanged(currentAttrName)).build()
                );
    }
}