/*
 * Copyright (c) 2014, 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.notification.mgt;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.BundleContext;
import org.wso2.carbon.identity.notification.mgt.bean.ModuleConfiguration;
import org.wso2.carbon.identity.notification.mgt.bean.Subscription;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.securevault.SecretResolver;
import org.wso2.securevault.SecretResolverFactory;
import org.wso2.securevault.commons.MiscellaneousUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;


/**
 * Configuration builder class for Message Management component. Responsible for reading msg-mgt
 * .properties file and extract properties and distribute them to relevant message sending
 * components.
 */
@SuppressWarnings("unused")
public class NotificationMgtConfigBuilder {

    private static final Log log = LogFactory.getLog(NotificationMgtConfigBuilder.class);
    /**
     * All properties configured in msg-mgt.properties file
     */
    private Properties notificationMgtConfigProperties;
    /**
     * Map of configurations which are specific to notification sending modules
     */
    private Map<String, ModuleConfiguration> moduleConfiguration;
    /**
     * Thread pool size for message sending task
     */
    private String threadPoolSize;

    /**
     * Load properties file and set Module properties
     *
     * @param bundleContext Bundle context
     * @throws NotificationManagementException
     */
    public NotificationMgtConfigBuilder(BundleContext bundleContext) throws NotificationManagementException {
        notificationMgtConfigProperties = loadProperties(bundleContext);
        setThreadPoolSize();
        resolveSecrets();
        moduleConfiguration = new HashMap<String, ModuleConfiguration>();
        build();
    }

    /**
     * Sets the thread pool size read from configurations
     */
    private void setThreadPoolSize() {
        threadPoolSize = (String) notificationMgtConfigProperties.remove(NotificationMgtConstants.Configs.
                THREAD_POOL_SIZE);
    }

    /**
     * Load properties which are defined in msg-mgt.properties file
     *
     * @param bundleContext Bundle context
     * @return Set of properties which are defined in msg-mgt.properties file
     * @throws NotificationManagementException
     */
    private Properties loadProperties(BundleContext bundleContext) throws NotificationManagementException {
        Properties properties = new Properties();
        InputStream inStream = null;

        // Open the default configuration file in carbon conf directory path .
        File MessageMgtPropertyFile = new File(CarbonUtils.getCarbonConfigDirPath() + File.separator + "identity" +
                File.separator, NotificationMgtConstants.MODULE_CONFIG_FILE);

        try {
            // If the configuration exists in the carbon conf directory, read properties from there
            if (MessageMgtPropertyFile.exists()) {
                inStream = new FileInputStream(MessageMgtPropertyFile);
                // Else read properties form either bundle context path or class path
            } else {
                URL url;
                if (bundleContext != null) {
                    if ((url = bundleContext.getBundle().getResource(NotificationMgtConstants.MODULE_CONFIG_FILE))
                            != null) {
                        inStream = url.openStream();
                    } else {
                        log.warn("Bundle context could not find resource " + NotificationMgtConstants
                                .MODULE_CONFIG_FILE);
                    }
                    // If bundle context is not found, read properties file from class path
                } else {

                    if ((url = this.getClass().getClassLoader().getResource(NotificationMgtConstants.
                            MODULE_CONFIG_FILE)) != null) {
                        inStream = url.openStream();
                    } else {
                        log.warn("Class resource loader could not find resource " + NotificationMgtConstants.
                                MODULE_CONFIG_FILE);
                    }
                }
            }

            if (inStream != null) {
                properties.load(inStream);
            }
            //Even if the configurations are not found, individual modules can behave themselves without configuration
        } catch (FileNotFoundException e) {
            log.warn("Could not find configuration file for Message Sending module", e);
        } catch (IOException e) {
            log.warn("Error while opening input stream for property file", e);
            // Finally close input stream
        } finally {
            try {
                if (inStream != null) {
                    inStream.close();
                }
            } catch (IOException e) {
                log.error("Error while closing input stream ", e);
            }
        }

        return properties;
    }

    /**
     * Build and store per module configuration objects
     */
    private void build() {
        Properties moduleNames = NotificationManagementUtils.getSubProperties(NotificationMgtConstants.Configs.
                MODULE_NAME, notificationMgtConfigProperties);
        Enumeration propertyNames = moduleNames.propertyNames();
        // Iterate through events and build event objects
        while (propertyNames.hasMoreElements()) {
            String key = (String) propertyNames.nextElement();
            String moduleName = (String) moduleNames.remove(key);
            moduleConfiguration.put(moduleName, buildModuleConfigurations(moduleName));
        }
    }

    /**
     * Building per module configuration objects
     *
     * @param moduleName Name of the module
     * @return ModuleConfiguration object which has configurations for the given module name
     */
    private ModuleConfiguration buildModuleConfigurations(String moduleName) {
        Properties moduleProperties = getModuleProperties(moduleName);
        List<Subscription> subscriptionList = buildSubscriptionList(moduleName, moduleProperties);

        return new ModuleConfiguration(getModuleProperties(moduleName), subscriptionList);
    }

    /**
     * Build a list of subscription by a particular module
     *
     * @param moduleName       Name of the module
     * @param moduleProperties Set of properties which
     * @return A list of subscriptions by the module
     */
    private List<Subscription> buildSubscriptionList(String moduleName, Properties moduleProperties) {
        // Get subscribed events
        Properties subscriptions = NotificationManagementUtils.getSubProperties(moduleName + "." +
                NotificationMgtConstants.Configs.SUBSCRIPTION, moduleProperties);

        List<Subscription> subscriptionList = new ArrayList<Subscription>();
        Enumeration propertyNames = subscriptions.propertyNames();
        // Iterate through events and build event objects
        while (propertyNames.hasMoreElements()) {
            String key = (String) propertyNames.nextElement();
            String subscriptionName = (String) subscriptions.remove(key);
            // Read all the event properties starting from the event prefix
            Properties subscriptionProperties = NotificationManagementUtils.getPropertiesWithPrefix
                    (moduleName + "." + NotificationMgtConstants.Configs.SUBSCRIPTION + "." + subscriptionName,
                            moduleProperties);
            Subscription subscription = new Subscription(subscriptionName, subscriptionProperties);
            subscriptionList.add(subscription);
        }
        return subscriptionList;
    }

    /**
     * Retrieve all properties defined for a particular module
     *
     * @param moduleName Name of the module
     * @return A set of properties which are defined for a particular module
     */
    private Properties getModuleProperties(String moduleName) {
        return NotificationManagementUtils.getPropertiesWithPrefix(moduleName, notificationMgtConfigProperties);
    }

    /**
     * Returns a module configuration object for the passed mdoule name
     *
     * @param moduleName Name of the module
     * @return Module configuration object which is relevant to the given name.
     */
    public ModuleConfiguration getModuleConfigurations(String moduleName) {
        return this.moduleConfiguration.get(moduleName);
    }

    public String getThreadPoolSize() {
        return threadPoolSize;
    }

    /**
     * There can be sensitive information like passwords in configuration file. If they are encrypted using secure
     * vault, this method will resolve them and replace with original values.
     */
    private void resolveSecrets() {

        SecretResolver secretResolver = SecretResolverFactory.create(notificationMgtConfigProperties);
        Enumeration propertyNames = notificationMgtConfigProperties.propertyNames();

        // Iterate through whole config file and find encrypted properties and resolve them
        if (secretResolver != null && secretResolver.isInitialized()) {
            for (Map.Entry<Object, Object> entry : notificationMgtConfigProperties.entrySet()) {
                String key = entry.getKey().toString();
                String value = entry.getValue().toString();
                if (value != null) {
                    value = MiscellaneousUtil.resolve(value, secretResolver);
                }
                notificationMgtConfigProperties.put(key, value);
            }
        } else {
            if (log.isDebugEnabled()) {
                log.debug("Secret Resolver is not present. Will not resolve encryptions in config file");
            }
        }
    }
}