/*
 * Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. 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.wso2.carbon.identity.application.common.processors;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.common.cache.RandomPasswordContainerCache;
import org.wso2.carbon.identity.application.common.cache.RandomPasswordContainerCacheEntry;
import org.wso2.carbon.identity.application.common.cache.RandomPasswordContainerCacheKey;
import org.wso2.carbon.identity.application.common.model.Property;
import org.wso2.carbon.identity.application.common.model.RandomPassword;
import org.wso2.carbon.identity.application.common.model.RandomPasswordContainer;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants;
import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil;

import java.util.ArrayList;
import java.util.UUID;

/**
 * Use this class to secure sensitive properties display on html front-end
 */
public class RandomPasswordProcessor {

    private static final Log log = LogFactory.getLog(RandomPasswordProcessor.class);

    private static volatile RandomPasswordProcessor randomPasswordProcessor = null;

    private RandomPasswordProcessor() {

    }

    public static RandomPasswordProcessor getInstance() {

        if (randomPasswordProcessor == null) {
            synchronized (RandomPassword.class) {
                if (randomPasswordProcessor == null) {
                    randomPasswordProcessor = new RandomPasswordProcessor();
                }
            }
        }

        return randomPasswordProcessor;
    }

    /**
     * Remove original passwords with random passwords when sending password properties to UI front-end
     *
     * @param properties
     */
    public Property[] removeOriginalPasswords(Property[] properties) {

        if (ArrayUtils.isEmpty(properties)) {
            return new Property[0];
        }

        properties = addUniqueIdProperty(properties);
        String uuid = IdentityApplicationManagementUtil
                .getPropertyValue(properties, IdentityApplicationConstants.UNIQUE_ID_CONSTANT);
        String randomPhrase = IdentityApplicationConstants.RANDOM_PHRASE_PREFIX + uuid;
        RandomPassword[] randomPasswords = replaceOriginalPasswordsWithRandomPasswords(
                randomPhrase, properties);
        if (!ArrayUtils.isEmpty(randomPasswords)) {
            addPasswordContainerToCache(randomPasswords, uuid);
        }

        return properties;
    }

    /**
     * Remove random passwords with original passwords when sending password properties to Service Back-end
     *
     * @param properties
     */
    public Property[] removeRandomPasswords(Property[] properties, boolean withCacheClear) {

        if (ArrayUtils.isEmpty(properties)) {
            return new Property[0];
        }

        String uuid = IdentityApplicationManagementUtil.getPropertyValue(properties,
                IdentityApplicationConstants.UNIQUE_ID_CONSTANT);
        if (StringUtils.isBlank(uuid)) {
            if (log.isDebugEnabled()) {
                log.debug("Cache Key not found for Random Password Container");
            }
        } else {
            properties = removeUniqueIdProperty(properties);
            RandomPassword[] randomPasswords = getRandomPasswordContainerFromCache(uuid, withCacheClear);
            if (!ArrayUtils.isEmpty(randomPasswords)) {
                replaceRandomPasswordsWithOriginalPasswords(properties,
                        randomPasswords);
            }
        }
        return properties;
    }

    private void addPasswordContainerToCache(RandomPassword[] randomPasswords, String uuid) {

        if (randomPasswords == null) {
            if (log.isDebugEnabled()) {
                log.debug("Random passwords not available for Password Container");
            }
        }
        RandomPasswordContainer randomPasswordContainer = new RandomPasswordContainer();
        randomPasswordContainer.setRandomPasswords(randomPasswords);
        randomPasswordContainer.setUniqueID(uuid);

        RandomPasswordContainerCacheKey cacheKey = new RandomPasswordContainerCacheKey(uuid);

        RandomPasswordContainerCacheEntry cacheEntry = new RandomPasswordContainerCacheEntry(randomPasswordContainer);

        if (log.isDebugEnabled()) {
            log.debug("Adding Random passwords to Cache with Key: " + uuid);
        }

        RandomPasswordContainerCache.getInstance().addToCache(cacheKey, cacheEntry);
    }

    private RandomPassword[] getRandomPasswordContainerFromCache(String uuid, boolean withCacheClear) {

        RandomPasswordContainerCacheKey cacheKey = new RandomPasswordContainerCacheKey(uuid);
        RandomPasswordContainerCache cache = RandomPasswordContainerCache.getInstance();
        RandomPasswordContainerCacheEntry cacheEntry = cache.getValueFromCache(cacheKey);
        if (cacheEntry == null) {
            if (log.isDebugEnabled()) {
                log.debug("Cache entry not found for cache key :" + uuid);
            }
            return new RandomPassword[0];
        }

        RandomPasswordContainer randomPasswordContainer = cacheEntry.getRandomPasswordContainer();

        if (randomPasswordContainer == null || randomPasswordContainer.getRandomPasswords() == null) {
            if (log.isDebugEnabled()) {
                log.debug("Could not find Random Passwords from Random Password Container");
            }
            return new RandomPassword[0];
        }

        if (withCacheClear) {
            if (log.isDebugEnabled()) {
                log.debug("Removing Cache Entry with Key: " + uuid);
            }
            cache.clearCacheEntry(cacheKey);
        }
        return randomPasswordContainer.getRandomPasswords();
    }

    /**
     * Replace original passwords with provided random phrase
     *
     * @param randomPhrase
     * @param properties
     * @return
     */
    private RandomPassword[] replaceOriginalPasswordsWithRandomPasswords(String randomPhrase, Property[] properties) {

        ArrayList<RandomPassword> randomPasswordArrayList = new ArrayList<RandomPassword>();
        if (properties != null) {
            for (Property property : properties) {

                if (property == null || property.getName() == null || property.getValue() == null) {
                    continue;
                }
                if (property.getName().contains(IdentityApplicationConstants.PASSWORD)) {

                    if (log.isDebugEnabled()) {
                        log.debug("Found Password Property :" + property.getValue());
                    }
                    RandomPassword randomPassword = new RandomPassword();
                    randomPassword.setPropertyName(property.getName());
                    randomPassword.setPassword(property.getValue());
                    randomPassword.setRandomPhrase(randomPhrase);
                    randomPasswordArrayList.add(randomPassword);

                    property.setValue(randomPhrase);
                }
            }
        }
        return randomPasswordArrayList.toArray(new RandomPassword[randomPasswordArrayList.size()]);
    }

    /**
     * Remove random password list with corresponding old original password if value has not been changed
     *
     * @param properties
     * @param randomPasswords
     */
    private void replaceRandomPasswordsWithOriginalPasswords(Property[] properties, RandomPassword randomPasswords[]) {

        if (ArrayUtils.isEmpty(properties) || ArrayUtils.isEmpty(randomPasswords)) {
            return;
        }

        for (Property property : properties) {
            if (property == null || StringUtils.isBlank(property.getName()) ||
                    StringUtils.isBlank(property.getValue())) {
                continue;
            }
            if (property.getName().contains(IdentityApplicationConstants.PASSWORD)) {
                for (RandomPassword randomPassword : randomPasswords) {
                    if (property.getName().equals(randomPassword.getPropertyName())) {
                        //if password property value equal to random phrase, user didn't change the password hence set
                        // old password
                        if (property.getValue().equals(randomPassword.getRandomPhrase())) {
                            if (log.isDebugEnabled()) {
                                log.debug("User didn't changed password property: " + property.getName() +
                                        " hence replace Random Password with Original Password");
                            }
                            property.setValue(randomPassword.getPassword());
                        }
                    }
                }
            }
        }
    }

    private Property[] addUniqueIdProperty(Property[] properties) {

        if (ArrayUtils.isEmpty(properties)) {
            return new Property[0];
        }

        String uuid = UUID.randomUUID().toString();
        Property uniqueIdProperty = new Property();
        uniqueIdProperty.setName(IdentityApplicationConstants.UNIQUE_ID_CONSTANT);
        uniqueIdProperty.setValue(uuid);
        if (log.isDebugEnabled()) {
            log.debug("Adding uniqueId property: " + uuid);
        }
        properties = (Property[]) ArrayUtils.add(properties, uniqueIdProperty);

        return properties;
    }

    private Property[] removeUniqueIdProperty(Property[] properties) {

        if (ArrayUtils.isEmpty(properties)) {
            return new Property[0];
        }

        for (int i = 0; i < properties.length; i++) {
            if (properties[i] == null) {
                continue;
            }
            if (IdentityApplicationConstants.UNIQUE_ID_CONSTANT.equals(properties[i].getName())) {
                Property[] propertiesTemp = properties;

                if (log.isDebugEnabled()) {
                    log.debug("Removing uniqueId property: " + properties[i].getName());
                }
                properties = (Property[]) ArrayUtils.removeElement(properties, properties[i]);
                //Removing uniqueId property from existing properties too
                propertiesTemp[i] = null;
            }
        }
        return properties;
    }
}
