// SPDX-License-Identifier: GPL-3.0-or-later

#include "health.h"
#include "health_internals.h"

static inline int health_parse_delay(
        size_t line, const char *filename, char *string,
        int *delay_up_duration,
        int *delay_down_duration,
        int *delay_max_duration,
        float *delay_multiplier) {

    char given_up = 0;
    char given_down = 0;
    char given_max = 0;
    char given_multiplier = 0;

    char *s = string;
    while(*s) {
        char *key = s;

        while(*s && !isspace((uint8_t)*s)) s++;
        while(*s && isspace((uint8_t)*s)) *s++ = '\0';

        if(!*key) break;

        char *value = s;
        while(*s && !isspace((uint8_t)*s)) s++;
        while(*s && isspace((uint8_t)*s)) *s++ = '\0';

        if(!strcasecmp(key, "up")) {
            if (!config_parse_duration(value, delay_up_duration)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
                                  line, filename, value, key);
            }
            else given_up = 1;
        }
        else if(!strcasecmp(key, "down")) {
            if (!config_parse_duration(value, delay_down_duration)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
                                  line, filename, value, key);
            }
            else given_down = 1;
        }
        else if(!strcasecmp(key, "multiplier")) {
            *delay_multiplier = strtof(value, NULL);
            if(isnan(*delay_multiplier) || isinf(*delay_multiplier) || islessequal(*delay_multiplier, 0)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
                                  line, filename, value, key);
            }
            else given_multiplier = 1;
        }
        else if(!strcasecmp(key, "max")) {
            if (!config_parse_duration(value, delay_max_duration)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
                                  line, filename, value, key);
            }
            else given_max = 1;
        }
        else {
            netdata_log_error("Health configuration at line %zu of file '%s': unknown keyword '%s'",
                              line, filename, key);
        }
    }

    if(!given_up)
        *delay_up_duration = 0;

    if(!given_down)
        *delay_down_duration = 0;

    if(!given_multiplier)
        *delay_multiplier = 1.0;

    if(!given_max) {
        if((*delay_max_duration) < (*delay_up_duration) * (*delay_multiplier))
            *delay_max_duration = (int)((*delay_up_duration) * (*delay_multiplier));

        if((*delay_max_duration) < (*delay_down_duration) * (*delay_multiplier))
            *delay_max_duration = (int)((*delay_down_duration) * (*delay_multiplier));
    }

    return 1;
}

static inline ALERT_ACTION_OPTIONS health_parse_options(const char *s) {
    ALERT_ACTION_OPTIONS options = ALERT_ACTION_OPTION_NONE;
    char buf[100+1] = "";

    while(*s) {
        buf[0] = '\0';

        // skip spaces
        while(*s && isspace((uint8_t)*s))
            s++;

        // find the next space
        size_t count = 0;
        while(*s && count < 100 && !isspace((uint8_t)*s))
            buf[count++] = *s++;

        if(buf[0]) {
            buf[count] = '\0';

            if(!strcasecmp(buf, "no-clear-notification") || !strcasecmp(buf, "no-clear"))
                options |= ALERT_ACTION_OPTION_NO_CLEAR_NOTIFICATION;
            else
                netdata_log_error("Ignoring unknown alarm option '%s'", buf);
        }
    }

    return options;
}

static inline int health_parse_repeat(
        size_t line,
        const char *file,
        char *string,
        uint32_t *warn_repeat_every,
        uint32_t *crit_repeat_every
) {

    char *s = string;
    while(*s) {
        char *key = s;

        while(*s && !isspace((uint8_t)*s)) s++;
        while(*s && isspace((uint8_t)*s)) *s++ = '\0';

        if(!*key) break;

        char *value = s;
        while(*s && !isspace((uint8_t)*s)) s++;
        while(*s && isspace((uint8_t)*s)) *s++ = '\0';

        if(!strcasecmp(key, "off")) {
            *warn_repeat_every = 0;
            *crit_repeat_every = 0;
            return 1;
        }
        if(!strcasecmp(key, "warning")) {
            if (!config_parse_duration(value, (int*)warn_repeat_every)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
                                  line, file, value, key);
            }
        }
        else if(!strcasecmp(key, "critical")) {
            if (!config_parse_duration(value, (int*)crit_repeat_every)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid value '%s' for '%s' keyword",
                                  line, file, value, key);
            }
        }
    }

    return 1;
}

static inline int isvariableterm(const char s) {
    if(isalnum(s) || s == '.' || s == '_')
        return 0;

    return 1;
}

static inline int health_parse_db_lookup(size_t line, const char *filename, char *string, struct rrd_alert_config *ac) {
    if(ac->dimensions) string_freez(ac->dimensions);
    ac->dimensions = NULL;
    ac->after = 0;
    ac->before = 0;
    ac->update_every = 0;
    ac->options = 0;
    ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_EQUAL;
    ac->time_group_value = NAN;

    char *s = string, *key;

    // first is the group method
    key = s;
    while(*s && !isspace((uint8_t)*s) && *s != '(') s++;
    while(*s && isspace((uint8_t)*s)) *s++ = '\0';
    if(!*s) {
        netdata_log_error("Health configuration invalid chart calculation at line %zu of file '%s': expected group method followed by the 'after' time, but got '%s'",
                          line, filename, key);
        return 0;
    }

    bool group_options = false;
    if(*s == '(') {
        *s++ = '\0';
        group_options = true;
    }

    if((ac->time_group = time_grouping_parse(key, RRDR_GROUPING_UNDEFINED)) == RRDR_GROUPING_UNDEFINED) {
        netdata_log_error("Health configuration at line %zu of file '%s': invalid group method '%s'",
                          line, filename, key);
        return 0;
    }

    if(group_options) {
        if(*s == '!') {
            s++;
            if(*s == '=') s++;
            ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
        }
        else if(*s == '<') {
            s++;
            if(*s == '>') {
                s++;
                ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_NOT_EQUAL;
            }
            else if(*s == '=') {
                s++;
                ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER_EQUAL;
            }
            else
                ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_GREATER;
        }
        else if(*s == '>') {
            if(*s == '=') {
                s++;
                ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS_EQUAL;
            }
            else
                ac->time_group_condition = ALERT_LOOKUP_TIME_GROUP_CONDITION_LESS;
        }

        while(*s && isspace((uint8_t)*s)) s++;

        if(*s) {
            if(isdigit((uint8_t)*s) || *s == '.') {
                ac->time_group_value = str2ndd(s, &s);
                while(s && *s && isspace((uint8_t)*s)) s++;

                if(!s || *s != ')') {
                    netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after number in aggregation method on '%s'",
                                      line, filename, key);
                    return 0;
                }
            }
        }
        else if(*s != ')') {
            netdata_log_error("Health configuration at line %zu of file '%s': missing closing parenthesis after method on '%s'",
                              line, filename, key);
            return 0;
        }

        s++;
    }

    switch (ac->time_group) {
        default:
            break;

        case RRDR_GROUPING_COUNTIF:
            if(isnan(ac->time_group_value))
                ac->time_group_value = 0;
            break;

        case RRDR_GROUPING_TRIMMED_MEAN:
        case RRDR_GROUPING_TRIMMED_MEDIAN:
            if(isnan(ac->time_group_value))
                ac->time_group_value = 5;
            break;

        case RRDR_GROUPING_PERCENTILE:
            if(isnan(ac->time_group_value))
                ac->time_group_value = 95;
            break;
    }

    // then is the 'after' time
    key = s;
    while(*s && !isspace((uint8_t)*s)) s++;
    while(*s && isspace((uint8_t)*s)) *s++ = '\0';

    if(!config_parse_duration(key, &ac->after)) {
        netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' after group method",
                          line, filename, key);
        return 0;
    }

    // sane defaults
    ac->update_every = ABS(ac->after);

    // now we may have optional parameters
    while(*s) {
        key = s;
        while(*s && !isspace((uint8_t)*s)) s++;
        while(*s && isspace((uint8_t)*s)) *s++ = '\0';
        if(!*key) break;

        if(!strcasecmp(key, "at")) {
            char *value = s;
            while(*s && !isspace((uint8_t)*s)) s++;
            while(*s && isspace((uint8_t)*s)) *s++ = '\0';

            if (!config_parse_duration(value, &ac->before)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
                                  line, filename, value, key);
            }
        }
        else if(!strcasecmp(key, HEALTH_EVERY_KEY)) {
            char *value = s;
            while(*s && !isspace((uint8_t)*s)) s++;
            while(*s && isspace((uint8_t)*s)) *s++ = '\0';

            if (!config_parse_duration(value, &ac->update_every)) {
                netdata_log_error("Health configuration at line %zu of file '%s': invalid duration '%s' for '%s' keyword",
                                  line, filename, value, key);
            }
        }
        else if(!strcasecmp(key, "absolute") || !strcasecmp(key, "abs") || !strcasecmp(key, "absolute_sum")) {
            ac->options |= RRDR_OPTION_ABSOLUTE;
        }
        else if(!strcasecmp(key, "min2max")) {
            ac->options |= RRDR_OPTION_DIMS_MIN2MAX;
        }
        else if(!strcasecmp(key, "average")) {
            ac->options |= RRDR_OPTION_DIMS_AVERAGE;
        }
        else if(!strcasecmp(key, "min")) {
            ac->options |= RRDR_OPTION_DIMS_MIN;
        }
        else if(!strcasecmp(key, "max")) {
            ac->options |= RRDR_OPTION_DIMS_MAX;
        }
        else if(!strcasecmp(key, "sum")) {
            ;
        }
        else if(!strcasecmp(key, "null2zero")) {
            ac->options |= RRDR_OPTION_NULL2ZERO;
        }
        else if(!strcasecmp(key, "percentage")) {
            ac->options |= RRDR_OPTION_PERCENTAGE;
        }
        else if(!strcasecmp(key, "unaligned")) {
            ac->options |= RRDR_OPTION_NOT_ALIGNED;
        }
        else if(!strcasecmp(key, "anomaly-bit")) {
            ac->options |= RRDR_OPTION_ANOMALY_BIT;
        }
        else if(!strcasecmp(key, "match-ids") || !strcasecmp(key, "match_ids")) {
            ac->options |= RRDR_OPTION_MATCH_IDS;
        }
        else if(!strcasecmp(key, "match-names") || !strcasecmp(key, "match_names")) {
            ac->options |= RRDR_OPTION_MATCH_NAMES;
        }
        else if(!strcasecmp(key, "of")) {
            char *find = NULL;
            if(*s && strcasecmp(s, "all") != 0) {
                find = strcasestr(s, " foreach");
                if(find) {
                    *find = '\0';
                }
                ac->dimensions = string_strdupz(s);
            }

            if(!find) {
                break;
            }
            s = ++find;
        }
        else {
            netdata_log_error("Health configuration at line %zu of file '%s': unknown keyword '%s'",
                              line, filename, key);
        }
    }

    return 1;
}

static inline STRING *health_source_file(size_t line, const char *file) {
    char buffer[FILENAME_MAX + 1];
    snprintfz(buffer, FILENAME_MAX, "line=%zu,file=%s", line, file);
    return string_strdupz(buffer);
}

char *health_edit_command_from_source(const char *source)
{
    char buffer[FILENAME_MAX + 1];
    char *temp = strdupz(source);
    char *line_num = strchr(temp, '@');
    char *line_p = temp;
    char *file_no_path = strrchr(temp, '/');

    // Check for the 'line=' format if '@' is not found
    if (!line_num) {
        line_num = strstr(temp, "line=");
        file_no_path = strstr(temp, "file=/");
    }

    if (likely(file_no_path && line_num)) {
        if (line_num == strchr(temp, '@')) {
            *line_num = '\0';  // Handle the old format
        } else {
            line_num += strlen("line=");
            file_no_path = strrchr(file_no_path + strlen("file="), '/');
            char *line_end = strchr(line_num, ',');
            if (line_end) {
                line_p = line_num;
                *line_end = '\0';
            }
        }

        snprintfz(
            buffer,
            FILENAME_MAX,
            "sudo %s/edit-config health.d/%s=%s=%s",
            netdata_configured_user_config_dir,
            file_no_path + 1,
            line_p,
            rrdhost_registry_hostname(localhost));
    } else {
        buffer[0] = '\0';
    }

    freez(temp);
    return strdupz(buffer);
}


static inline void strip_quotes(char *s) {
    while(*s) {
        if(*s == '\'' || *s == '"') *s = ' ';
        s++;
    }
}

static void replace_green_red(RRD_ALERT_PROTOTYPE *ap, NETDATA_DOUBLE green, NETDATA_DOUBLE red) {
    if(!isnan(green)) {
        STRING *green_str = string_strdupz("green");
        expression_hardcode_variable(ap->config.calculation, green_str, green);
        expression_hardcode_variable(ap->config.warning, green_str, green);
        expression_hardcode_variable(ap->config.critical, green_str, green);
        string_freez(green_str);
    }

    if(!isnan(red)) {
        STRING *red_str = string_strdupz("red");
        expression_hardcode_variable(ap->config.calculation, red_str, red);
        expression_hardcode_variable(ap->config.warning, red_str, red);
        expression_hardcode_variable(ap->config.critical, red_str, red);
        string_freez(red_str);
    }
}

static void dims_grouping_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
    if(ap->config.options & RRDR_OPTION_DIMS_MIN)
        ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN;
    else if(ap->config.options & RRDR_OPTION_DIMS_MAX)
        ap->config.dims_group = ALERT_LOOKUP_DIMS_MAX;
    else if(ap->config.options & RRDR_OPTION_DIMS_MIN2MAX)
        ap->config.dims_group = ALERT_LOOKUP_DIMS_MIN2MAX;
    else if(ap->config.options & RRDR_OPTION_DIMS_AVERAGE)
        ap->config.dims_group = ALERT_LOOKUP_DIMS_AVERAGE;
    else
        ap->config.dims_group = ALERT_LOOKUP_DIMS_SUM;
}

static void lookup_data_source_from_rrdr_options(RRD_ALERT_PROTOTYPE *ap) {
    if(ap->config.options & RRDR_OPTION_PERCENTAGE)
        ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_PERCENTAGES;
    else if(ap->config.options & RRDR_OPTION_ANOMALY_BIT)
        ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_ANOMALIES;
    else
        ap->config.data_source = ALERT_LOOKUP_DATA_SOURCE_SAMPLES;
}

#define PARSE_HEALTH_CONFIG_LOG_DUPLICATE_STRING_MSG(ax, member) do {                               \
    if(strcmp(string2str(ax->member), value) != 0)                                                  \
        netdata_log_error(                                                                          \
            "Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' twice, "     \
            "once with value '%s' and later with value '%s'. Using ('%s').",                        \
            line, filename, string2str(ac->name), key,                                              \
            string2str(ax->member), value, value);                                                  \
} while(0)

#define PARSE_HEALTH_CONFIG_LINE_STRING(ax, member) do {                                            \
    if(ax->member) {                                                                                \
        PARSE_HEALTH_CONFIG_LOG_DUPLICATE_STRING_MSG(ax, member);                                   \
        string_freez(ax->member);                                                                   \
    }                                                                                               \
    ax->member = string_strdupz(value);                                                             \
} while(0)

#define PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(ax, member, label) do {                             \
    const char *_label = label;                                                                     \
    if(_label && !*_label)                                                                          \
        _label = NULL;                                                                              \
                                                                                                    \
    if(value && (!*value || strcmp(value, "*") == 0))                                               \
        value = NULL;                                                                               \
    else if(value && (strcmp(value, "!* *") == 0 || strcmp(value, "!*") == 0)) {                    \
        value = NULL;                                                                               \
        ap->match.enabled = false;                                                                  \
    }                                                                                               \
                                                                                                    \
    if(value && !_label && !strchr(value, '=')) {                                                   \
        netdata_log_error(                                                                          \
            "Health configuration at line %zu of file '%s' for alarm '%s' has key '%s' "            \
            "with value '%s' that does not match label=pattern. Ignoring it.",                      \
            line, filename, string2str(ac->name), key, value);                                      \
        value = NULL;                                                                               \
    }                                                                                               \
                                                                                                    \
    if(value) {                                                                                     \
        typeof(ax->member) _old = ax->member;                                                       \
        char _buf[strlen(value) + string_strlen(_old) + (_label ? strlen(_label) : 0) + 3];         \
        snprintfz(_buf, sizeof(_buf), "%s%s%s%s%s",                                                 \
                      _label ? _label : "",                                                         \
                      _label ? "=" : "",                                                            \
                      value,                                                                        \
                      _old ? " " : "",                                                              \
                      _old ? string2str(_old) : "");                                                \
        string_freez(_old);                                                                         \
        ax->member = string_strdupz(_buf);                                                          \
    }                                                                                               \
} while(0)

int health_readfile(const char *filename, void *data __maybe_unused, bool stock_config) {
    netdata_log_debug(D_HEALTH, "Health configuration reading file '%s'", filename);

    static uint32_t
            hash_alarm = 0,
            hash_template = 0,
            hash_os = 0,
            hash_on = 0,
            hash_host = 0,
            hash_plugin = 0,
            hash_module = 0,
            hash_calc = 0,
            hash_green = 0,
            hash_red = 0,
            hash_warn = 0,
            hash_crit = 0,
            hash_exec = 0,
            hash_every = 0,
            hash_lookup = 0,
            hash_units = 0,
            hash_summary = 0,
            hash_info = 0,
            hash_class = 0,
            hash_component = 0,
            hash_type = 0,
            hash_recipient = 0,
            hash_delay = 0,
            hash_options = 0,
            hash_repeat = 0,
            hash_host_label = 0,
            hash_chart_label = 0;

    char buffer[HEALTH_CONF_MAX_LINE + 1];

    if(unlikely(!hash_alarm)) {
        hash_alarm = simple_uhash(HEALTH_ALARM_KEY);
        hash_template = simple_uhash(HEALTH_TEMPLATE_KEY);
        hash_on = simple_uhash(HEALTH_ON_KEY);
        hash_os = simple_uhash(HEALTH_OS_KEY);
        hash_host = simple_uhash(HEALTH_HOST_KEY);
        hash_plugin = simple_uhash(HEALTH_PLUGIN_KEY);
        hash_module = simple_uhash(HEALTH_MODULE_KEY);
        hash_calc = simple_uhash(HEALTH_CALC_KEY);
        hash_lookup = simple_uhash(HEALTH_LOOKUP_KEY);
        hash_green = simple_uhash(HEALTH_GREEN_KEY);
        hash_red = simple_uhash(HEALTH_RED_KEY);
        hash_warn = simple_uhash(HEALTH_WARN_KEY);
        hash_crit = simple_uhash(HEALTH_CRIT_KEY);
        hash_exec = simple_uhash(HEALTH_EXEC_KEY);
        hash_every = simple_uhash(HEALTH_EVERY_KEY);
        hash_units = simple_hash(HEALTH_UNITS_KEY);
        hash_summary = simple_hash(HEALTH_SUMMARY_KEY);
        hash_info = simple_hash(HEALTH_INFO_KEY);
        hash_class = simple_uhash(HEALTH_CLASS_KEY);
        hash_component = simple_uhash(HEALTH_COMPONENT_KEY);
        hash_type = simple_uhash(HEALTH_TYPE_KEY);
        hash_recipient = simple_hash(HEALTH_RECIPIENT_KEY);
        hash_delay = simple_uhash(HEALTH_DELAY_KEY);
        hash_options = simple_uhash(HEALTH_OPTIONS_KEY);
        hash_repeat = simple_uhash(HEALTH_REPEAT_KEY);
        hash_host_label = simple_uhash(HEALTH_HOST_LABEL_KEY);
        hash_chart_label = simple_uhash(HEALTH_CHART_LABEL_KEY);
    }

    FILE *fp = fopen(filename, "r");
    if(!fp) {
        netdata_log_error("Health configuration cannot read file '%s'.", filename);
        return 0;
    }

    RRD_ALERT_PROTOTYPE *ap = NULL;
    struct rrd_alert_config *ac = NULL;
    struct rrd_alert_match *am = NULL;
    NETDATA_DOUBLE green = NAN;
    NETDATA_DOUBLE red = NAN;

    size_t line = 0, append = 0;
    char *s;
    while((s = fgets(&buffer[append], (int)(HEALTH_CONF_MAX_LINE - append), fp)) || append) {
        int stop_appending = !s;
        line++;
        s = trim(buffer);
        if(!s || *s == '#') continue;

        append = strlen(s);
        if(!stop_appending && s[append - 1] == '\\') {
            s[append - 1] = ' ';
            append = &s[append] - buffer;
            if(append < HEALTH_CONF_MAX_LINE)
                continue;
            else
                netdata_log_error(
                    "Health configuration has too long multi-line at line %zu of file '%s'.",
                    line, filename);
        }
        append = 0;

        char *key = s;
        while(*s && *s != ':') s++;
        if(!*s) {
            netdata_log_error(
                "Health configuration has invalid line %zu of file '%s'. It does not contain a ':'. Ignoring it.",
                line, filename);
            continue;
        }
        *s = '\0';
        s++;

        char *value = s;
        key = trim_all(key);
        value = trim_all(value);

        if(!key) {
            netdata_log_error(
                "Health configuration has invalid line %zu of file '%s'. Keyword is empty. Ignoring it.",
                line, filename);

            continue;
        }

        if(!value) {
            netdata_log_error(
                "Health configuration has invalid line %zu of file '%s'. value is empty. Ignoring it.",
                line, filename);
            continue;
        }

        uint32_t hash = simple_uhash(key);

        if((hash == hash_alarm && !strcasecmp(key, HEALTH_ALARM_KEY)) || (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY))) {
            if(ap) {
                lookup_data_source_from_rrdr_options(ap);
                dims_grouping_from_rrdr_options(ap);
                replace_green_red(ap, green, red);
                health_prototype_add(ap, NULL);
                freez(ap);
            }

            ap = callocz(1, sizeof(*ap));
            am = &ap->match;
            ac = &ap->config;

            {
                char *tmp = strdupz(value);
                if(rrdvar_fix_name(tmp))
                    netdata_log_error("Health configuration renamed alarm '%s' to '%s'", value, tmp);

                ap->config.name = string_strdupz(tmp);
                freez(tmp);
            }

            ap->_internal.enabled = true;
            ap->match.enabled = true;
            ap->match.is_template = (hash == hash_template && !strcasecmp(key, HEALTH_TEMPLATE_KEY));
            ap->config.source = health_source_file(line, filename);
            ap->config.source_type = stock_config ? DYNCFG_SOURCE_TYPE_STOCK : DYNCFG_SOURCE_TYPE_USER;
            green = NAN;
            red = NAN;
            ap->config.delay_multiplier = 1;
            ap->config.warn_repeat_every = health_globals.config.default_warn_repeat_every;
            ap->config.crit_repeat_every = health_globals.config.default_crit_repeat_every;
        }
        else if(!am || !ac || !ap) {
            netdata_log_error(
                "Health configuration at line %zu of file '%s' has unknown key '%s'. "
                "Expected either '" HEALTH_ALARM_KEY "' or '" HEALTH_TEMPLATE_KEY "'.",
                line, filename, key);
        }
        else if(!am->is_template && hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_STRING(am, on.chart);
        }
        else if(am->is_template && hash == hash_on && !strcasecmp(key, HEALTH_ON_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_STRING(am, on.context);
        }
        else if(hash == hash_os && !strcasecmp(key, HEALTH_OS_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, host_labels, "_os");
        }
        else if(hash == hash_host && !strcasecmp(key, HEALTH_HOST_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, host_labels, "_hostname");
        }
        else if(hash == hash_host_label && !strcasecmp(key, HEALTH_HOST_LABEL_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, host_labels, NULL);
        }
        else if(hash == hash_plugin && !strcasecmp(key, HEALTH_PLUGIN_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, chart_labels, "_collect_plugin");
        }
        else if(hash == hash_module && !strcasecmp(key, HEALTH_MODULE_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, chart_labels, "_collect_module");
        }
        else if(hash == hash_chart_label && !strcasecmp(key, HEALTH_CHART_LABEL_KEY)) {
            PARSE_HEALTH_CONFIG_LINE_PATTERN_APPEND(am, chart_labels, NULL);
        }
        else if(hash == hash_class && !strcasecmp(key, HEALTH_CLASS_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, classification);
        }
        else if(hash == hash_component && !strcasecmp(key, HEALTH_COMPONENT_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, component);
        }
        else if(hash == hash_type && !strcasecmp(key, HEALTH_TYPE_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, type);
        }
        else if(hash == hash_lookup && !strcasecmp(key, HEALTH_LOOKUP_KEY)) {
            health_parse_db_lookup(line, filename, value, ac);
        }
        else if(hash == hash_every && !strcasecmp(key, HEALTH_EVERY_KEY)) {
            if(!config_parse_duration(value, &ac->update_every))
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
                    "cannot parse duration: '%s'.",
                    line, filename, string2str(ac->name), key, value);
        }
        else if(hash == hash_green && !strcasecmp(key, HEALTH_GREEN_KEY)) {
            char *e;
            green = str2ndd(value, &e);
            if(e && *e) {
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
                    "leaves this string unmatched: '%s'.",
                    line, filename, string2str(ac->name), key, e);
            }
        }
        else if(hash == hash_red && !strcasecmp(key, HEALTH_RED_KEY)) {
            char *e;
            red = str2ndd(value, &e);
            if(e && *e) {
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
                    "leaves this string unmatched: '%s'.",
                    line, filename, string2str(ac->name), key, e);
            }
        }
        else if(hash == hash_calc && !strcasecmp(key, HEALTH_CALC_KEY)) {
            const char *failed_at = NULL;
            int error = 0;
            ac->calculation = expression_parse(value, &failed_at, &error);
            if(!ac->calculation) {
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
                    "has non-parseable expression '%s': %s at '%s'",
                    line, filename, string2str(ac->name), key, value, expression_strerror(error), failed_at);
                am->enabled = false;
            }
        }
        else if(hash == hash_warn && !strcasecmp(key, HEALTH_WARN_KEY)) {
            const char *failed_at = NULL;
            int error = 0;
            ac->warning = expression_parse(value, &failed_at, &error);
            if(!ac->warning) {
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
                    "has non-parseable expression '%s': %s at '%s'",
                    line, filename, string2str(ac->name), key, value, expression_strerror(error), failed_at);
                am->enabled = false;
            }
        }
        else if(hash == hash_crit && !strcasecmp(key, HEALTH_CRIT_KEY)) {
            const char *failed_at = NULL;
            int error = 0;
            ac->critical = expression_parse(value, &failed_at, &error);
            if(!ac->critical) {
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm '%s' at key '%s' "
                    "has non-parseable expression '%s': %s at '%s'",
                    line, filename, string2str(ac->name), key, value, expression_strerror(error), failed_at);
                am->enabled = false;
            }
        }
        else if(hash == hash_exec && !strcasecmp(key, HEALTH_EXEC_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, exec);
        }
        else if(hash == hash_recipient && !strcasecmp(key, HEALTH_RECIPIENT_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, recipient);
        }
        else if(hash == hash_units && !strcasecmp(key, HEALTH_UNITS_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, units);
        }
        else if(hash == hash_summary && !strcasecmp(key, HEALTH_SUMMARY_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, summary);
        }
        else if(hash == hash_info && !strcasecmp(key, HEALTH_INFO_KEY)) {
            strip_quotes(value);
            PARSE_HEALTH_CONFIG_LINE_STRING(ac, info);
        }
        else if(hash == hash_delay && !strcasecmp(key, HEALTH_DELAY_KEY)) {
            health_parse_delay(line, filename, value,
                               &ac->delay_up_duration, &ac->delay_down_duration,
                               &ac->delay_max_duration, &ac->delay_multiplier);
        }
        else if(hash == hash_options && !strcasecmp(key, HEALTH_OPTIONS_KEY)) {
            ac->alert_action_options |= health_parse_options(value);
        }
        else if(hash == hash_repeat && !strcasecmp(key, HEALTH_REPEAT_KEY)){
            health_parse_repeat(line, filename, value,
                                &ac->warn_repeat_every,
                                &ac->crit_repeat_every);
            ac->has_custom_repeat_config = true;
        }
        else {
            if (strcmp(key, "families") != 0 && strcmp(key, "charts") != 0)
                netdata_log_error(
                    "Health configuration at line %zu of file '%s' for alarm/template '%s' has unknown key '%s'.",
                    line, filename, string2str(ac->name), key);
        }
    }

    if(ap) {
        lookup_data_source_from_rrdr_options(ap);
        dims_grouping_from_rrdr_options(ap);
        replace_green_red(ap, green, red);
        health_prototype_add(ap, NULL);
        freez(ap);
    }

    fclose(fp);
    return 1;
}
