package org.ovirt.engine.core.notifier.utils;

import javax.mail.internet.InternetAddress;

import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.core.uutils.config.ShellLikeConfd;

/**
 * Defines properties uses by the event notification service
 */
public class NotificationProperties extends ShellLikeConfd {

    /**
     * Service parameters
     */
    public static final String LOG_LEVEL = "LOG_LEVEL";
    public static final String DAYS_TO_KEEP_HISTORY = "DAYS_TO_KEEP_HISTORY";
    public static final String INTERVAL_IN_SECONDS = "INTERVAL_IN_SECONDS";
    public static final String ENGINE_INTERVAL_IN_SECONDS = "ENGINE_INTERVAL_IN_SECONDS";
    public static final String ENGINE_TIMEOUT_IN_SECONDS = "ENGINE_TIMEOUT_IN_SECONDS";
    public static final String IS_HTTPS_PROTOCOL = "IS_HTTPS_PROTOCOL";
    public static final String SSL_PROTOCOL = "SSL_PROTOCOL";
    public static final String REPEAT_NON_RESPONSIVE_NOTIFICATION = "REPEAT_NON_RESPONSIVE_NOTIFICATION";
    public static final String ENGINE_MONITOR_RETRIES = "ENGINE_MONITOR_RETRIES";
    public static final String SSL_IGNORE_CERTIFICATE_ERRORS = "SSL_IGNORE_CERTIFICATE_ERRORS";
    public static final String SSL_IGNORE_HOST_VERIFICATION = "SSL_IGNORE_HOST_VERIFICATION";

    /**
     * Idle task interval
     * Interval in seconds to perform low priority tasks.
     */
    public static final String IDLE_INTERVAL = "IDLE_INTERVAL";

    /**
     * Comma separated list of recipients to be informed in case the notification service cannot connect to the DB. can
     * be empty.
     */
    public static final String FAILED_QUERIES_NOTIFICATION_RECIPIENTS = "FAILED_QUERIES_NOTIFICATION_RECIPIENTS";

    /**
     * Send a notification email after first failure to fetch notifications, and then once every
     * failedQueriesNotificationThreshold times.
     */
    public static final String FAILED_QUERIES_NOTIFICATION_THRESHOLD = "FAILED_QUERIES_NOTIFICATION_THRESHOLD";

    /**
     * This parameter specifies how many days of old events are processed and sent when the notifier starts. If set to
     * 2, for example, the notifier will process and send the events of the last two days, older events will just be
     * marked as processed and won't be sent.
     */
    public static final String DAYS_TO_SEND_ON_STARTUP = "DAYS_TO_SEND_ON_STARTUP";

    private static final String GENERIC_MESSAGE = "Check configuration file, ";

    // Default files for defaults and overridden values:
    private static String DEFAULTS_PATH = "/usr/share/ovirt-engine/conf/notifier.conf.defaults";
    private static String VARS_PATH = "/etc/ovirt-engine/notifier/notifier.conf";

    // This is a singleton and this is the instance:
    private static NotificationProperties instance;

    private NotificationProperties() {
        // Locate the defaults file and add it to the list:
        String defaultsPath = System.getenv("ENGINE_NOTIFIER_DEFAULTS");
        if (defaultsPath == null) {
            defaultsPath = DEFAULTS_PATH;
        }

        // Locate the overridden values file and add it to the list:
        String varsPath = System.getenv("ENGINE_NOTIFIER_VARS");
        if (varsPath == null) {
            varsPath = VARS_PATH;
        }

        loadConfig(defaultsPath, varsPath);
    }

    public static synchronized NotificationProperties getInstance() {
        if (instance == null) {
            instance = new NotificationProperties();
        }
        return instance;
    }

    public static void setDefaults(String defaultsPath, String varsPath) {
        DEFAULTS_PATH = defaultsPath;
        VARS_PATH = varsPath;
    }

    public static void release() {
        instance = null;
    }

    /**
     * Validates properties values.
     *
     * @throws IllegalArgumentException
     *             if some properties has invalid values
     */
    public void validate() {
        validateCommon();
    }

    private void validateCommon() {
        requireAll(DAYS_TO_KEEP_HISTORY,
                DAYS_TO_SEND_ON_STARTUP,
                FAILED_QUERIES_NOTIFICATION_THRESHOLD,
                ENGINE_INTERVAL_IN_SECONDS,
                ENGINE_TIMEOUT_IN_SECONDS,
                INTERVAL_IN_SECONDS,
                IS_HTTPS_PROTOCOL,
                REPEAT_NON_RESPONSIVE_NOTIFICATION,
                IDLE_INTERVAL);

        // validate non negative args
        for (String property : new String[] {
                DAYS_TO_KEEP_HISTORY,
                DAYS_TO_SEND_ON_STARTUP,
                FAILED_QUERIES_NOTIFICATION_THRESHOLD,
                IDLE_INTERVAL }) {
            validateNonNegetive(property);
        }
    }

    public boolean isConfigured(String property) {
        return !StringUtils.isEmpty(getProperty(property, true));
    }

    public void requireAll(String... mandatoryProperties) {
        for (String property : mandatoryProperties) {
            if (StringUtils.isEmpty(getProperty(property, true))) {
                throw new IllegalArgumentException(
                        String.format(
                                GENERIC_MESSAGE + "'%s' is missing",
                                property));
            }
        }
    }

    public void requireOne(String... mandatoryProperties) {
        boolean provided = false;
        for (String property : mandatoryProperties) {
            if (isConfigured(property)) {
                provided = true;
            }
        }
        if (!provided) {
            StringBuilder sb = new StringBuilder(100);
            sb.append(GENERIC_MESSAGE);
            String prefix = " ";
            for (String property : mandatoryProperties) {
                sb.append(prefix).append(property);
                prefix = " or ";
            }
            sb.append(" must be defined");
            throw new IllegalArgumentException(
                    sb.toString());
        }
    }

    public int validateInteger(String property) {
        try {
            return Integer.parseInt(getProperty(property));
        } catch (NumberFormatException exception) {
            throw new IllegalArgumentException(
                    String.format(
                            "'%s' must be an integer.",
                            property));
        }
    }

    public int validateNonNegetive(String property) {
        int value = validateInteger(property);
        if (value < 0) {
            throw new IllegalArgumentException(
                    String.format(
                            "'%s' must be a non negative integer.",
                            property));
        }
        return value;
    }

    public int validatePort(String property) {
        int value = validateInteger(property);
        if (value < 1 || value > 0xffff) {
            throw new IllegalArgumentException(
                    String.format(
                            "'%s' must be a a valid port.",
                            property));
        }
        return value;
    }

    public InternetAddress validateEmail(String property) {
        try {
            InternetAddress ret = null;

            String value = getProperty(property);

            if (!StringUtils.isEmpty(value)) {
                ret = new InternetAddress(value);
            }

            return ret;
        } catch (Exception ex) {
            throw new IllegalArgumentException(
                    String.format(
                            GENERIC_MESSAGE + "invalid format in '%s'",
                            property),
                    ex);
        }
    }
}
