<?php
require_once APPPATH . 'modules/dashboard/models/entities/CF_Alert.php';
require_once APPPATH . 'modules/dashboard/models/entities/CF_CustomNotificationScripts.php';

class alerts_model extends Cf_Model
{
    public $collectionName = 'dashboard_alerts';
    public $variables_dictionary = 'variables_dictionary';

    public const ROOT_USERNAME = 'root';
    public const CFAPACHE_USERNAME = 'cfapache';

    public $defaultFilter;

    public $allowedActions;

    // we need this for email
    // NOTE: on CLI mode 'local' time zone is invalid because it rely on session, see getDateStatus
    public $timeZone = 'gmt'; // FIXME - we should deside something about time zones
    private $cacheTTL;

    public function __construct()
    {
        parent::__construct();

        $this->load->library('dashboard/InventoryAlert');
        $this->load->library('dashboard/PolicyAlert');
        $this->load->library('dashboard/SoftwareUpdateAlert');
        $this->load->library('dashboard/CustomAlert');
        $this->load->library('dashboard/FileChangedAlert');
        $this->load->driver('cache', ['adapter' => 'file']);
        $this->load->model('host_model');
        $this->customalert->setRestClient($this->getRestClient());
        $this->filechangedalert->setRestClient($this->getRestClient());
        $this->cacheTTL = $this->config->item('default_cache_ttl');
    }

    public function setDefaultFilter($filter)
    {
        $this->defaultFilter = $filter;
    }

    // TODO _ refactor this function -remove from here and make it available for all alerts
    public function utils_getIncludesFromAlert($alert)
    {
        if (empty($alert->hostContexts) || !isset($alert->hostContexts['includes'])) {
            $includes = [];
        } else {
            $includes = $alert->hostContexts['includes'];
        }

        return $includes;
    }

    public function utils_getExcludesFromAlert($alert)
    {
        if (empty($alert->excludedHosts) || !isset($alert->excludedHosts['excludes'])) {
            $excludes = [];
        } else {
            $excludes = $alert->excludedHosts['excludes'];
        }

        return $excludes;
    }

    public function savealert($data, $overwrite = false, $is_admin = false)
    {
        if (!is_array($data)) {
            $this->setError('Invalid parameter passed to function');

            return false;
        }

        // set username as default filter
        $filter = [];
        $filter['username'] = $data['username'];

        // use widget id to allow duplicate names on different widgets - 3.7 dashboard sharing
        if (isset($data['widgetid'])) {
            $filter['widgetid'] = $data['widgetid'];
        }

        // if id provided - we edit existing alert
        if (isset($data['id']) && !empty($data['id'])) {
            $filter['id'] = $data['id'];
        } else {
            // if no id - trying to find item by name
            $filter['name'] = $data['name'];
        }

        $this->setDefaultFilter($filter);

        if ($this->validate($data, $overwrite, $is_admin)) {
            $id = '';

            $filter = $this->defaultFilter;

            $currentItem = $this->get_only_alerts($filter);

            if (!empty($currentItem)) {
                $id = $currentItem[0]->id;
                $reportOwner = $currentItem[0]->getUsername();

                // alert  owner. if this alert created by user and get's overwritten by admin, we will try to keep original owner
                // $data['username'] - user who created report, in most cases current logged user
                if ($reportOwner !== $data['username'] && $is_admin === true) {
                    $data['username'] = $reportOwner;
                }
            }

            try {
                $customNotificationScripts = !empty($data['customNotificationSelection'])
                    ? $data['customNotificationSelection']
                    : [];

                $insertData = $this->_map_insert_data($data);
                if ($id == '') {
                    // not exist - insert it will return inserted item

                    $this->db->insert($this->collectionName, $insertData);
                    $id = $this->db->insert_id();
                } else {
                    // if exist - update, and then select inserted item
                    $this->db
                        ->where([
                            'id' => $id,
                        ])
                        ->update($this->collectionName, $insertData);
                }

                $this->updateCustomAlertScripts($id, $customNotificationScripts);
            } catch (Exception $e) {
                $message = $e->getMessage();
                log_message('debug', 'Unable to save alert. Error: ' . $message);
                throw new Exception($message);
            }

            // get object from DB
            $offset = '';
            $limit = '';
            $order = [];

            $alertObj = $this->get_only_alerts(['id' => intval($id)], $offset, $limit, $order, $is_admin);

            if ($alertObj) {
                return $alertObj[0];
            }
        }

        return false;
    }

    public function _map_insert_data($data)
    {
        if (!empty($data['hostContexts'])) {
            $data['hostContexts'] = json_encode($data['hostContexts']);
        }

        if (isset($data['excludedHosts'])) {
            $data['excludedHosts'] = json_encode($data['excludedHosts']);
        }

        unset($data['customNotificationSelection']);

        return $data;
    }

    public function validate($data, $overwrite = false, $is_admin = false)
    {
        $valid = true;

        if (!isset($data['username']) || trim($data['username']) == '') {
            $valid = false;
            $this->setError('username is empty');

            return false;
        }

        if (!isset($data['name']) || trim($data['name']) == '') {
            $valid = false;
            $this->setError($this->lang->line('dashboard_alert_name_empty'));

            return false;
        }

        if (!isset($data['severity']) || trim($data['severity']) == '') {
            $valid = false;
            $this->setError($this->lang->line('dashboard_alert_severity_empty'));

            return false;
        }

        // show message if name is already in use by this user
        $item = $this->_nameAlreadyExist($data);
        if (!empty($item)) {
            $valid = false;
            $this->setError($this->lang->line('dashboard_alert_already_exists'), 422); //422 Unprocessable Entity
        }

        if (!empty($item) && ($is_admin === false && $this->_alertOwner($data) === false)) {
            $this->clearErrors();
            $this->setError($this->lang->line('dashboard_alert_already_exists'), 422); //422 Unprocessable Entity
            $valid = false;
        }

        return $valid;
    }

    /**
     * Returns only alerts info. Alert status is not recalculated
     *
     * Primary use case - run alert function where we don't need alert status info
     *
     *
     * @param array|\type $filter
     * @param string|\type $offset
     * @param string|\type $limit
     * @param array|\type $order
     * @param bool|\type $is_admin
     *
     * @return \CF_Alert
     */
    public function get_only_alerts($filter = [], $offset = '', $limit = '', $order = [])
    {
        if (!empty($limit)) {
            $this->db->limit($limit, $offset);
        }
        if (!empty($order)) {
            reset($order);
            $field = key($order);
            $orderValue = current($order);
            $this->db->order_by($field, $orderValue);
        }

        $query = $this->db->where($filter)->get($this->collectionName);

        $result = [];

        foreach ((array) $query->result_array() as $alertsResult) {
            $obj = new CF_Alert($alertsResult);
            $result[] = $obj;
        }

        return $result;
    }

    public function get_alerts_with_ids($ids, $filter, $order = [])
    {
        if (!empty($ids)) {
            $this->db->where_in('id', $ids);
        }

        if (!empty($order)) {
            reset($order);
            $field = key($order);
            $orderValue = current($order);
            $this->db->order_by($field, $orderValue);
        }

        $query = $this->db->where($filter)->get($this->collectionName);

        $alertList = [];

        foreach ((array) $query->result_array() as $alertsResult) {
            $obj = new CF_Alert($alertsResult);
            $alertList[] = $obj;
        }

        $alertsList = $this->_calculateAlertStatus($alertList);

        return $alertList;
    }

    /**
     * Return alert info with RECALCULATED status info. Status is recaclulated only for SQL based alerts
     *
     * This function MUST be used in every place where we need live status info
     *
     * @param array|\type $filter
     * @param string|\type $offset
     * @param string|\type $limit
     * @param array|\type $order
     * @param bool|\type $is_admin
     *
     * @return type
     */
    public function get_all_alerts($filter = [], $offset = '', $limit = '', $order = [], $is_admin = false)
    {
        if (!empty($limit)) {
            $this->db->limit($limit, $offset);
        }
        if (!empty($order)) {
            reset($order);
            $field = key($order);
            $orderValue = current($order);
            $this->db->order_by($field, $orderValue);
        }

        $alertsList = $this->get_only_alerts($filter, $offset, $limit, $order);
        $alertsList = $this->_calculateAlertStatus($alertsList);

        return $alertsList;
    }

    public function _calculateAlertStatus($alertsList = null)
    {
        $combinedAlerts = $this->_getCombinedRulesAndAlertsFilterByType($alertsList, 'all');

        if (empty($combinedAlerts)) {
            return $alertsList;
        }

        // get current status for alert
        foreach ($combinedAlerts as $item => $combinedObj) {
            $alert = &$combinedObj['alert'];
            $rule = &$combinedObj['rule'];
            $includes = &$combinedObj['includes'];
            $excludes = &$combinedObj['excludes'];
            $alertStatusInfo = $this->_getAlertStatus($alert, $rule, $includes, $excludes);

            $alert->status = $alertStatusInfo['status'];

            if (isset($alertStatusInfo['failHosts'])) {
                $alert->failHosts = (int) $alertStatusInfo['failHosts'];
            }

            if (isset($alertStatusInfo['totalHosts'])) {
                $alert->totalHosts = (int) $alertStatusInfo['totalHosts'];
            }

            $alert->lastEventTime = time();
        }

        // replace $alertsList object with new object and status
        foreach ($alertsList as $item => $alertObj) {
            if (isset($combinedAlerts[$alertObj->id])) {
                $alertObj = $combinedAlerts[$alertObj->id]['alert'];
            }
        }

        return $alertsList;
    }

    /**
     * CLI process which alert alerts
     *
     * We will alert only alerts which are in widgets, other alerts - ignored
     *
     * @param <int> $delay - delay between alert revalidation
     * @param <int> $limit - how many alerts would be processed
     * @param <string> $type - filter by type, alowed values - all | sql
     *
     * @return boolean
     * @throws Exception
     *
     *
     *
     * $alertStatus - array with the following fields:
     * $alertStatus['failHosts']    = how many failed hosts
     * $alertStatus['successHosts'] = how many success hosts //FIXME: we probably don't need this field
     * $alertStatus['totalHosts']   = how many host  //FIXME: this is dynamic variable. because in DC case we can ectivete cketch on new host(s)
     *
     *
     * $alertStatus['lastalert'] = current timestamp
     * $alertStatus['pause']     = -reset pause If alert is active - this mean that alert is no longer paused, so we should reset these fields
     * $alertStatus['paused']    = -reset paused
     *
     * alert will be updated with information from $alertStatus
     */

    // TODO: optimization needed
    //FIXME: Limit won't help. we should probably add some time indicators to widgets too,
    // this will help us to get info about all widgets without being overloaded
    public function runalerts($delay, $limit, $type)
    {
        $offset = '';
        // order by desc since last run
        $order = [
            'lastCheck' => 'DESC',
        ];
        $is_admin = true;

        $where = sprintf(
            '(lastcheck is NULL or lastcheck < %s) AND ((pause != -1 AND pause < %s)  OR pause is NULL)',
            time() - (int) $delay,
            time(),
        );

        $alertsList = $this->get_only_alerts($where, $offset, $limit, $order);

        if (empty($alertsList)) {
            $message = 'runalerts(): Unable to find alerts.';
            log_message('debug', "runalerts(): $message");

            return false;
        }

        // --------------
        // refactor
        $this->load->model('dashboard/rules_model');
        // 1. get rules
        $rulesIdList = [];

        foreach ($alertsList as $item => $alert) {
            if (!empty($alert->ruleid)) {
                $rulesIdList[$alert->ruleid] = $alert->ruleid;
            }
        }

        if (empty($rulesIdList)) {
            $message = 'runalerts(): Unable to find alert rules.';
            log_message('debug', "runalerts(): $message");

            return false;
        }

        $rulesFilter = [];
        $rulesId = array_values($rulesIdList);

        if ($type !== 'all') {
            $rulesFilter['type'] = $type;
        }

        $rulesTmp = $this->rules_model->get_rules_with_ids($rulesId, $rulesFilter);

        if (empty($rulesTmp)) {
            $message = 'runalerts(): Unable to find rules.';
            log_message('debug', "runalerts(): $message");

            return false;
        }

        $rulesList = [];
        foreach ($rulesTmp as $item => $rule) {
            $rulesList[$rule->id] = $rule;
        }

        // --------------

        $combinedAlerts = $this->_getCombinedRulesAndAlertsFilterByType($alertsList, $type);

        foreach ($combinedAlerts as $combinedItem => $combinedObj) {
            $alert = &$combinedObj['alert'];
            $rule = &$combinedObj['rule'];
            $includes = &$combinedObj['includes'];
            $excludes = &$combinedObj['excludes'];

            $currentTime = time(); // because some request can take a lot of time, we need same values everywhere

            log_message(
                'debug',
                'runalerts(): alert: ' . $alert->name,
                ' rule' . $rule->name . ' rule type: ' . $rule->type,
            );

            $alertStatusInfo = $this->_getAlertStatus($alert, $rule, $includes, $excludes);

            // add additional fields to $alertStatus
            $alertStatusInfo['lastCheck'] = $currentTime;
            $alertStatusInfo['pause'] = 0;
            $alertStatusInfo['paused'] = 0;

            if (isset($alert->status) && !empty($alert->status)) {
                $previousalertStatus = $alert->status;
            } else {
                $previousalertStatus = 'success'; // default status for the first alert
            }

            $currentalertStatus = $alertStatusInfo['status'];

            if ($currentalertStatus !== $previousalertStatus) {
                $alertStatusInfo['lastStatusChange'] = $currentTime;
            }

            // rules how to create events and notifications: https://cfengine.com/dev/versions/show/115#Notifications
            try {
                if ($currentalertStatus !== $previousalertStatus) {
                    // status is changed
                    $alertStatusInfo['lastEventTime'] = $currentTime;
                    $alertStatusInfo['lastStatusChange'] = $currentTime;

                    $this->_createAlertEventRecord($alert, $alertStatusInfo, $rulesList[$alert->ruleid]);
                    $this->getScriptsToRun($alert, $alertStatusInfo, $rulesList[$alert->ruleid]);

                    if (!empty($alert->emailToNotify) && $alert->emailToNotify != 'NULL') {
                        $this->_createEmailAboutEvent($alert, $alertStatusInfo, $rulesList[$alert->ruleid]);
                    }
                } else {
                    // generate event for each check ONLY if it is still failing

                    // NO EVENT IF ALERT REMAINS FAIL, but send reminder if neededs
                    //
                    // send reminder if status = fail
                    // reminder is set
                    // and this is a time for reminder

                    if (
                        $currentalertStatus == 'fail' &&
                        !empty($alert->reminder) &&
                        $currentTime >= $alert->lastEventTime + $alert->reminder
                    ) {
                        $alertStatusInfo['lastEventTime'] = $currentTime;
                        $alertStatusInfo['lastStatusChange'] = $alert->getLastStatusChange();
                        $this->getScriptsToRun($alert, $alertStatusInfo, $rulesList[$alert->ruleid]);
                        if (!empty($alert->emailToNotify)) {
                            $this->_createEmailAboutEvent($alert, $alertStatusInfo, $rulesList[$alert->ruleid]);
                        }
                    }
                }

                // update current alert in the end !!
                $this->updatealertById($alert->id, $alertStatusInfo);
            } catch (Exception $e) {
                log_message(
                    'error',
                    'runalerts(): ' . $e->getMessage() . ' ' . $e->getFile() . ' line:' . $e->getLine(),
                );
                throw $e;
            }
        } // foreach

        return true;
    }

    private function _getAlertStatus($alert, $rule, $includes, $excludes)
    {
        $alertStatusInfo = [];

        if (empty($rule->type)) {
            return;
        }

        try {
            switch ($rule->type) {
                case 'softwareupdate':
                    $alertStatusInfo = $this->softwareupdatealert->getSoftwareUpdateStatus(
                        $alert,
                        $rule,
                        $includes,
                        $excludes,
                    );
                    break;

                case 'inventory':
                    $alertStatusInfo = $this->_getInventoryStatus($alert, $rule, $includes, $excludes);
                    break;

                case 'policy':
                    $alertStatusInfo = $this->policyalert->getPolicyStatus($alert, $rule, $includes, $excludes);
                    break;

                case 'custom':
                    $alertStatusInfo = $this->customalert->getCustomStatus($alert, $rule, $includes, $excludes);
                    break;

                case 'fileChanged':
                    $alertStatusInfo = $this->filechangedalert->getStatus($alert, $rule, $includes, $excludes);
                    break;
            } // switch
        } catch (HttpClient_Forbidden $e) {
            $alertStatusInfo['status'] = 'internal_error';
        } catch (Exception $e) {
            $message = sprintf(
                "Alerts: unable to get alert status for alert: '%s', message: %s ",
                $alert->name,
                $e->getMessage(),
            );
            log_message(log_level_for_exception($e), $message . ' ' . $e->getFile() . ' line:' . $e->getLine());

            $alertStatusInfo['status'] = 'internal_error';
        }

        return $alertStatusInfo;
    }

    /**
     * Return array with rules and alerts like
     *  a [i][alert]
     *  a [i][rule]
     *  a [i][includes]
     *
     *
     * @param type $alertsList
     * @param type $ruleType - all/sql
     *
     * @return array
     */
    public function _getCombinedRulesAndAlertsFilterByType($alertsList, $ruleType)
    {
        $this->load->model('dashboard/rules_model');

        // 1. get rules
        $rulesIdList = [];

        foreach ($alertsList as $item => $alert) {
            if (!empty($alert->ruleid)) {
                $rulesIdList[$alert->ruleid] = $alert->ruleid;
            }
        }

        if (empty($rulesIdList)) {
            $message = 'runalerts(): Unable to find alert rules.';
            log_message('debug', "runalerts(): $message");

            return;
        }

        $rulesFilter = [];
        $rulesFilterId = array_values($rulesIdList);

        if ($ruleType !== 'all') {
            $rulesFilter['type'] = $ruleType;
        }

        $rulesTmp = $this->rules_model->get_rules_with_ids($rulesFilterId, $rulesFilter);

        if (empty($rulesTmp)) {
            $message = 'runalerts(): Unable to find rules.';
            log_message('debug', "runalerts(): $message");

            return;
        }

        // 2. make array where key is a rule id;
        $rulesList = [];
        foreach ($rulesTmp as $item => $rule) {
            $rulesList[$rule->id] = $rule;
        }

        $combinedAlerts = []; // store data from all tables (alerts and rules) - resolve all references

        foreach ($alertsList as $alertItem => $alert) {
            $index = $alert->id;

            if (isset($rulesList[$alert->ruleid])) {
                $combinedAlerts[$index]['alert'] = $alert;
                $combinedAlerts[$index]['rule'] = $rulesList[$alert->ruleid];
                $combinedAlerts[$index]['includes'] = $this->utils_getIncludesFromAlert($alert);
                $combinedAlerts[$index]['excludes'] = $this->utils_getExcludesFromAlert($alert);
            }
        }

        return $combinedAlerts;
    }

    public function deletealert($id, $username, $is_admin)
    {
        // delete from widgets
        // for now we can not share alerts, so we select only user widgets

        $widgetFilter = [];
        $alertFilter = [];

        // admin can delete any alert
        if ($is_admin !== true) {
            $alertFilter['username'] = $username;
        }

        //0. check if alert exist
        $alertFilter['id'] = intval($id);
        $alert = $this->get_only_alerts($alertFilter);

        if (empty($alert)) {
            $message = $this->lang->line('dashboard_alert_not_exist') . ' searched alert id=' . $id;
            log_message('error', $message);
            throw new Exception($message);
        }

        // remove from alerts
        try {
            $return = $this->db->where($alertFilter)->delete($this->collectionName);

            return $return;
        } catch (Exception $e) {
            log_message(log_level_for_exception($e), $e->getMessage() . ' ' . $e->getFile() . ' line:' . $e->getLine());
            throw $e;
        }
    }

    /**
     * Return limited list of hosts, totalHosts, link to signle report +  2 additional links to group reports
     *
     * all_flat_report      - All host flat report - similar to single host
     * all_summary_report   - Summary report, for example host name and count of promises according to rule
     *
     *  Result:
     *  array['hosts'] => [ hostkey, hostname, count, link_sql, link_includes]
     *  array['links'] => [
     *                      'all_flat_report'    => link to flat report
     *                      'all_summary_report' => link to summary report
     *                    ]
     * array['totalHosts'] => X
     *
     * @param type $id
     * @param type $username
     * @param type $is_admin
     *
     * @return array <array>
     */
    public function getalerthosts($id, $username, $is_admin, $load_limit)
    {
        // 1. get alert
        // 2. depends on the type - return host list

        $filter = [];

        $filter['id'] = intval($id);
        /*
        if (!$is_admin)
        {
            $filter['username'] = $username;
        }
        */

        $offset = '';
        $limit = 1;
        $order = [];

        $alerts = $this->get_only_alerts($filter, $offset, $limit, $order);

        if (empty($alerts)) {
            log_message('error', 'getalerthosts(): Unable to find alert with id: ' . $id);

            return [];
        }

        $alertObj = $alerts[0];

        // get associated rule
        $this->load->model('rules_model');

        $ruleFilter = [
            'id' => $alertObj->getRuleId(),
        ];
        $rules = $this->rules_model->get_all_rules($ruleFilter);

        if (empty($rules)) {
            log_message('error', 'getalerthosts(): Unable to find rule with id: ' . $alertObj->ruleid);

            return [];
        }

        $ruleObj = $rules[0];

        $hosts = [];

        switch ($ruleObj->type) {
            case 'policy':
                // get list of failing hosts
                $hostsListTmp = $this->policyalert->getFailHostsList_ForPolicyAlerts($alertObj, $ruleObj, $load_limit);

                $groupIncludes = $this->policyalert->utils_getIncludesFromAlert($alertObj);
                $groupExcludes = $this->policyalert->utils_getExcludesFromAlert($alertObj);
                $all_flat_report = $this->policyalert->getLinkToPromiseReport_PolicyAlerts($ruleObj, '');

                $all_summary_report = rawurlencode(
                    base64_encode($this->policyalert->get_SQLSTRING_HostlistAndCountPromisesByCondition($ruleObj)),
                );

                if (!empty($groupIncludes)) {
                    $groupIncludes = rawurlencode(
                        base64_encode(json_encode($this->policyalert->utils_getIncludesFromAlert($alertObj))),
                    );
                    $all_summary_report .= '/includes/' . $groupIncludes;
                    $all_flat_report .= '/includes/' . $groupIncludes;
                }
                if (!empty($groupExcludes)) {
                    $groupExcludes = rawurlencode(
                        base64_encode(json_encode($this->policyalert->utils_getExcludesFromAlert($alertObj))),
                    );
                    $all_summary_report .= '/excludes/' . $groupExcludes;
                    $all_flat_report .= '/excludes/' . $groupExcludes;
                }

                $totalHosts = $this->policyalert->getCountFailHosts_ForPolicyAlerts($alertObj, $ruleObj);

                // convert result from API to the same format as on SQL
                if (!empty($hostsListTmp)) {
                    $i = 0;
                    foreach ($hostsListTmp as $item => $value) {
                        $hosts[$i]['hostkey'] = $value['0'];
                        $hosts[$i]['hostname'] = $value['1'];
                        $hosts[$i]['triggered'] = $value['2'];
                        $hosts[$i]['link_sql'] = $this->policyalert->getLinkToPromiseReport_PolicyAlerts(
                            $ruleObj,
                            $value['0'],
                        );
                        $i++;
                    }
                }
                break;

            case 'inventory':
                $all_flat_report = '';
                $all_summary_report = '';
                $totalHosts = $this->inventoryalert->getCountFailHosts(
                    $alertObj,
                    $ruleObj,
                    Cf_RestInstance::getRestClient(),
                );
                $hosts = $this->inventoryalert->getInventoryHostListWithHeader(
                    $alertObj,
                    $ruleObj,
                    $load_limit,
                    Cf_RestInstance::getRestClient(),
                );
                break;

            case 'softwareupdate':
                // 1. link to expanded repot with package version/software - expanede list of packages  -
                // $all_flat_report
                //
                // 2. aggregation for all hosts by count packages
                // $all_summary_report

                $all_flat_report = '';

                $all_flat_report = $this->softwareupdatealert->getLinkToSoftwareUpdatesReport($ruleObj, '');
                $all_summary_report = rawurlencode(
                    base64_encode($this->softwareupdatealert->get_SQLSTRING_HostlistAndCountAvailableUpdates($ruleObj)),
                );

                $groupIncludes = $this->softwareupdatealert->utils_getIncludesFromAlert($alertObj);
                $groupExcludes = $this->softwareupdatealert->utils_getExcludesFromAlert($alertObj);
                if (!empty($groupIncludes)) {
                    $groupIncludes = rawurlencode(base64_encode(json_encode($groupIncludes)));
                    $all_flat_report = $all_flat_report . '/includes/' . $groupIncludes;
                    $all_summary_report .= '/includes/' . $groupIncludes;
                }

                if (!empty($groupExcludes)) {
                    $groupExcludes = rawurlencode(base64_encode(json_encode($groupExcludes)));
                    $all_flat_report = $all_flat_report . '/excludes/' . $groupExcludes;
                    $all_summary_report .= '/excludes/' . $groupExcludes;
                }

                $totalHosts = $this->softwareupdatealert->getCountFailHosts($alertObj, $ruleObj);

                // get list of failing hosts
                $hostsListTmp = $this->softwareupdatealert->getFailHostsList_ForSoftwareUpdateAlerts(
                    $alertObj,
                    $ruleObj,
                    $load_limit,
                );
                // convert result from API to the same format as on SQL
                if (!empty($hostsListTmp)) {
                    $i = 0;
                    foreach ($hostsListTmp as $item => $value) {
                        $hosts[$i]['hostkey'] = $value['0'];
                        $hosts[$i]['hostname'] = $value['1'];
                        $hosts[$i]['triggered'] = $value['2'];
                        $hosts[$i]['link_sql'] = $this->softwareupdatealert->getLinkToSoftwareUpdatesReport(
                            $ruleObj,
                            $value['0'],
                        );
                        $i++;
                    }
                }

                break;
            case 'custom':
                $all_flat_report = '';
                $all_summary_report = '';
                $totalHosts = $this->customalert->getCountFailHosts($alertObj, $ruleObj);
                $hosts = $this->customalert->getHostListWithHeader($alertObj, $ruleObj, $load_limit);

                break;
            case 'fileChanged':
                $all_flat_report = '';
                $all_summary_report = '';
                $totalHosts = $this->filechangedalert->getCountFailHosts($alertObj, $ruleObj);
                $hosts = $this->filechangedalert->getHostListWithHeader($alertObj, $ruleObj, $load_limit);

                break;
        }

        $result = [];

        $result['hosts'] = $hosts;
        $result['totalHosts'] = $totalHosts;
        $result['links'] = [
            'all_flat_report' => $all_flat_report,
            'all_summary_report' => $all_summary_report,
        ];

        return $result;
    }

    /// ------------------------ alert-related functions  -----------------------------------------

    /**
     * Update alert in DB
     *
     * @param <string> $id - alert id
     * @param <array> $data - alert data
     *
     * @return boolean
     * @throws Exception
     */
    public function updatealertById($id, $data)
    {
        if (empty($id)) {
            return false;
        }

        try {
            $this->db->where([
                'id' => $id,
            ]);
            $insertData = $this->_map_insert_data($data);

            return $this->db->update($this->collectionName, $insertData);
        } catch (Exception $e) {
            $message = $e->getMessage();
            log_message('debug', 'Unable to update alert. Error: ' . $message);
            throw new Exception($message);
        }
    }

    public function updateAlert($filter, $data)
    {
        try {
            $this->db->where($filter);
            $insertData = $this->_map_insert_data($data);
            $this->db->update($this->collectionName, $insertData);

            return true;
        } catch (Exception $e) {
            $message = $e->getMessage();
            log_message('debug', 'Unable to update alert. Error: ' . $message);
            throw new Exception($message);
        }
    }

    public function _calculatePercentage($fail, $total)
    {
        $fail = (int) $fail;

        $total = (int) $total;

        if ($total === 0 && $fail === 0) {
            return 0;
        }

        if ($total === 0 && $fail !== 0) {
            return 100;
        }

        return number_format(($fail / $total) * 100, 0);
    }

    /**
     * Create record in event log about alert
     *
     * @param <CF_alert> $alert
     * @param <array> $alertStatus
     * @param <CF_rule> $rule
     *
     * @return <string> event id
     */
    public function _createAlertEventRecord($alert, $alertStatus, $rule)
    {
        $this->load->model('eventslog_model');

        $eventRecord = [];
        $eventRecord['username'] = $alert->username;
        $eventRecord['item_id'] = $alert->id;
        $eventRecord['item_type'] = 'alerts';
        $eventRecord['item_name'] = $alert->name;

        $eventRecord['tags'] = ['dashboard', 'alerts', $alert->id, $alert->name, $rule->id, $rule->name, $rule->type];
        $eventRecord['time'] = time();
        $eventRecord['severity'] = $alert->severity;

        switch ($alertStatus['status']) {
            case 'fail':
                $eventRecord['message'] =
                    'Alert "' .
                    $alert->name .
                    '" triggered for ' .
                    $alertStatus['failHosts'] .
                    ' of ' .
                    $alertStatus['totalHosts'] .
                    ' hosts.';
                break;

            case 'internal_error':
                // note - same message is used for email
                $eventRecord['message'] =
                    'Alert "' .
                    $alert->name .
                    '" failed due to an internal error. Please check either Mission Portal or Apache logs for more details.';
                break;

            default:
                $eventRecord['message'] = 'Alert "' . $alert->name . '" cleared.';
        }

        try {
            $eventId = $this->eventslog_model->save($eventRecord);
        } catch (Exception $e) {
            $message = $e->getMessage();
            log_message('debug', '_createAlertEventRecord(). Unable to save event. Error: ' . $message);
            throw new Exception($message);
        }

        return $eventId;
    }

    public function getScriptsToRun($alert, $alertStatusInfo, $rule)
    {
        $this->load->model('eventslog_model');
        $scripts = [];
        $alertId = $alert->getId();
        log_message('debug', 'ALERT ID : ' . $alertId);
        if ($alertId) {
            $sql = sprintf(
                'SELECT * from dashboard_scripts where id IN ( SELECT script_id from dashboard_alerts_script where alert_id = ?)',
            );
            log_message('debug', 'script query : ' . $sql);
            $query = $this->db->query($sql, [$alertId]);

            foreach ($query->result_array() as $script) {
                $arguments = [];
                $arguments['ALERT_ID'] = $alertId;
                $arguments['ALERT_NAME'] = $alert->name;
                $arguments['ALERT_SEVERITY'] = $alert->severity;
                $arguments['ALERT_LAST_CHECK'] = intval($alert->lastCheck, 10);
                $arguments['ALERT_LAST_EVENT_TIME'] = intval($alert->lastEventTime, 10);
                $arguments['ALERT_LAST_STATUS_CHANGE'] = intval($alert->lastStatusChange, 10);

                $arguments['ALERT_STATUS'] = $alertStatusInfo['status'];
                $arguments['ALERT_FAILED_HOST'] = $alertStatusInfo['failHosts'] ?? 0;
                $arguments['ALERT_TOTAL_HOST'] = $alertStatusInfo['totalHosts'] ?? 0;
                $arguments['ALERT_CONDITION_NAME'] = $rule->name;
                $arguments['ALERT_CONDITION_DESCRIPTION'] = $rule->description;
                $arguments['ALERT_CONDITION_TYPE'] = $rule->type;

                $alertConditionArguments = $this->getAlertConditionArguments($rule);
                $arguments = array_merge($arguments, $alertConditionArguments);

                $tmpParameterFile = @tempnam('./public/tmp', 'CF_ALERT_PARAMS');
                chmod($tmpParameterFile, 0755);

                $handle = fopen($tmpParameterFile, 'w');
                foreach ($arguments as $key => $value) {
                    // replace quotes with '\\' for bash
                    $string = sprintf("%s='%s'%s", $key, str_replace("'", "'\\''", $value), PHP_EOL);
                    fwrite($handle, $string);
                }
                fclose($handle);

                $command = $script['script_name'];

                $scriptDir = realpath($this->config->item('notification_script_dir'));
                $filePath = $scriptDir . DIRECTORY_SEPARATOR . $command;

                $eventRecord = [];

                $eventRecord['username'] = $alert->username;
                $eventRecord['item_id'] = $alert->id; // reference to the alert
                $eventRecord['item_type'] = 'script';
                $eventRecord['item_name'] = $script['script_name'] . '->' . $alert->name;

                $eventRecord['tags'] = [
                    'email',
                    'dashboard',
                    'alerts',
                    $alert->id,
                    $alert->name,
                    $rule->id,
                    $rule->name,
                    $rule->type,
                ];
                $eventRecord['time'] = time();
                $eventRecord['severity'] = 'high';

                if (is_file($filePath) && is_executable($filePath)) {
                    $output = null;
                    $user = $this->config->item('notification_script_user');
                    if (empty($user)) {
                        $user = 'cfapache'; // default user
                    }
                    $command = $filePath . ' ' . $tmpParameterFile;

                    switch ($this->getUsernameOfProcessOwner()) {
                        case self::ROOT_USERNAME:
                            $executeCommand = sprintf("su %s -c '%s 2>&1'", $user, $command);
                            break;
                        case self::CFAPACHE_USERNAME:
                            $executeCommand = sprintf('%s 2>&1', $command);
                            break;
                        default:
                            log_message(
                                'error',
                                'Custom script is running by ' .
                                    $this->getUsernameOfProcessOwner() .
                                    ' user. Only root and cfapache are permitted to run custom scripts.',
                            );
                            break;
                    }

                    exec($executeCommand, $output, $returnStatus);
                    $outputString = implode(PHP_EOL, $output);
                    if ($outputString) {
                        $truncateOutput =
                            strlen($outputString) > 350 ? substr($outputString, 0, 350) . '...' : $outputString;
                        $outputString = ' with output: ' . $truncateOutput;
                    }
                    // generate event about script
                    if ($returnStatus == 0) {
                        $eventRecord['status'] = 'success';
                        $eventRecord['message'] = sprintf(
                            'Custom action "%s" ran successfully for alert "%s"%s',
                            $script['script_name'],
                            $alert->name,
                            $outputString,
                        );
                    } else {
                        $eventRecord['status'] = 'fail';
                        $eventRecord['message'] = sprintf(
                            'Custom action "%s" failed for alert "%s"%s',
                            $script['script_name'],
                            $alert->name,
                            $outputString,
                        );
                    }
                } else {
                    $eventRecord['status'] = 'fail';
                    $eventRecord['message'] = sprintf(
                        'Cannot find script named %s or it is not executable with path %s ',
                        $script['script_name'],
                        $filePath,
                    );
                }

                $this->eventslog_model->save($eventRecord);
                unlink($tmpParameterFile);
            }
        }
    }

    private function getUsernameOfProcessOwner()
    {
        return posix_getpwuid(posix_geteuid())['name'];
    }

    /**
     * @param int/null $id id of specific alert
     *
     * @return array list of notification scripts
     */
    public function getAllNotificationScripts($id = null)
    {
        $sql = 'SELECT * FROM dashboard_scripts s ';
        $bind = [];
        if (!empty($id)) {
            $bind[] = $id;
            $sql .= ' INNER JOIN dashboard_alerts_script  on script_id = s.id where alert_id = ? ';
        }
        $query = $this->db->query($sql, $bind);
        $scriptList = [];
        foreach ($query->result_array() as $script) {
            $scriptList[] = new CF_CustomNotificationsScripts($script);
        }

        return $scriptList;
    }

    public function getNotificationScript($id)
    {
        $sql = 'SELECT * FROM dashboard_scripts where id=?';
        $query = $this->db->query($sql, [$id]);
        $script = null;
        if ($query->num_rows() > 0) {
            $row = $query->row_array();
            $script = new CF_CustomNotificationsScripts($row);
        }

        return $script;
    }

    public function updateAlertScripts($script, $data)
    {
        $id = $script->getId();
        if (empty($data['scriptFileName'])) {
            $data['scriptFileName'] = $script->getScriptFileName();
        }
        $updateData = [
            'name' => $data['name'],
            'description' => $data['description'],
            'script_name' => $data['scriptFileName'],
        ];

        $this->db->where('id', $id);

        return $this->db->update('dashboard_scripts', $updateData);
    }

    public function insertAlertScripts($data)
    {
        $insertData = [
            'name' => $data['name'],
            'description' => $data['description'],
            'script_name' => $data['scriptFileName'],
        ];

        $this->db->where('name', $data['name']);
        $this->db->from('dashboard_scripts');
        $countUnique = $this->db->count_all_results();
        if ($countUnique > 0) {
            throw new Exception('Script name should be unique');
        } else {
            $result = $this->db->insert('dashboard_scripts', $insertData);
            if (!$result) {
                $error = $this->db->_error_message();
                throw new Exception($error);
            }
        }

        return true;
    }

    public function deleteAlertScript($id)
    {
        $result = false;
        $script = $this->getNotificationScript($id);
        if ($script) {
            $deleteData = ['id' => $id];
            $scriptDir = realpath($this->config->item('notification_script_dir'));
            log_message('debug', 'real path of the script :: ', $scriptDir);
            unlink($scriptDir . DIRECTORY_SEPARATOR . $script->getScriptFileName());
            $result = $this->db->delete('dashboard_scripts', $deleteData);
        }

        return $result;
    }

    /**
     * Create event and send email about event
     *
     * @param <CF_alert> $alert
     * @param <array> $alertStatus
     * @param <CF_rule> $rule
     */
    private function _createEmailAboutEvent($alert, $alertStatus, $rule)
    {
        $this->load->model('eventslog_model');

        $this->load->library('cfe_file_reports_utils');
        $this->load->model('settings_model');

        try {
            $this->load->model('MailSettingsModel');
            $defaultFromEmail = $this->MailSettingsModel->getSetting('default_from_email');
            $from = $defaultFromEmail
                ? $defaultFromEmail
                : trim($this->settings_model->app_settings_get_item('appemail'));
        } catch (Exception $e) {
            log_message('debug', '_createEmailAboutEvent(): Trying to set mail from with error: ' . $e->getMessage());
            $from = trim($this->settings_model->app_settings_get_item('appemail'));
        }

        $to = $alert->emailToNotify;

        $msg = '';

        switch ($alertStatus['status']) {
            case 'success':
                $subject = 'CFEngine alert for "' . $alert->name . '" cleared';
                $msg .= $this->_generateSuccessMsg($alert, $alertStatus);
                break;

            case 'internal_error':
                $subject = 'CFEngine alert for "' . $alert->name . '" cleared';
                $msg .= $this->_generateInternalErrorMsg($alert, $alertStatus);
                break;

            default:
                $subject = 'CFEngine alert for "' . $alert->name . '" triggered';
                $msg .= $this->_generateFailMsg($alert, $alertStatus);
        }

        $msg .= "\n\n-------\nGenerated: " . getDateStatus(time(), true, true, $this->timeZone);

        try {
            log_message('debug', '_createEmailAboutEvent(): Trying to send email to: ' . $to);
            $message = $this->cfe_file_reports_utils->emailReport($to, $from, $subject, $msg);
            log_message('debug', '_createEmailAboutEvent(): Message successfully sent to: ' . $to);

            // generate event about email send
            $eventRecord = [];

            $eventRecord['username'] = $alert->username;
            $eventRecord['item_id'] = $alert->id; // reference to the alert
            $eventRecord['item_type'] = 'email';
            $eventRecord['item_name'] = 'email';
            $eventRecord['status'] = 'success';

            if ($alertStatus['status'] === 'fail') {
                $eventRecord['message'] =
                    'Notification about triggered alert "' . $alert->name . '" sent to ' . $to . '.';
            } else {
                $eventRecord['message'] =
                    'Notification about cleared alert "' . $alert->name . '" sent to ' . $to . '.';
            }

            $eventRecord['tags'] = [
                'email',
                'dashboard',
                'alerts',
                $alert->id,
                $alert->name,
                $rule->id,
                $rule->name,
                $rule->type,
            ];
            $eventRecord['time'] = time();
            $eventRecord['severity'] = 'low';

            $this->eventslog_model->save($eventRecord);

            return true;
        } catch (Exception $e) {
            $message = 'alerts->_createEvent: ' . $e->getMessage();
            log_message('error', $message);

            // generate event about email send
            $eventRecord = [];
            $eventRecord['username'] = $alert->username;
            $eventRecord['item_id'] = $alert->id;
            $eventRecord['item_type'] = 'email';
            $eventRecord['item_name'] = 'email';
            $eventRecord['status'] = 'fail';
            $eventRecord['message'] =
                'Unable to send email about triggered alert: ' . $alert->name . '. Error: ' . $e->getMessage();
            $eventRecord['tags'] = [
                'email',
                'dashboard',
                'alerts',
                $alert->id,
                $alert->name,
                $rule->id,
                $rule->name,
                $rule->type,
            ];
            $eventRecord['time'] = time();
            $eventRecord['severity'] = 'low';

            $this->eventslog_model->save($eventRecord);

            return false;
        }
    }

    /**
     * Return status of inventory alert
     *
     * @param <object> $alert
     * @param <object> $rule
     * @param <array> $includes
     * @param <array> $excludes
     *
     * @return <array>
     */
    private function _getInventoryStatus($alert, $rule, $includes, $excludes)
    {
        $inventoryConditions = $rule->getInventoryCondition();
        // we have SQL string from DB, we just have to wrap it with count query
        $this->load->model('advancedreports_model');

        $failHosts = 0;

        if (!empty($inventoryConditions['filters'])) {
            $cacheKey = md5(json_encode($inventoryConditions['filters']) . $rule->username . json_encode([]));

            if (!($result = $this->cache->get($cacheKey))) {
                $result = $this->inventoryalert->getStatForInventory(
                    $inventoryConditions['filters'],
                    $includes,
                    $excludes,
                    Cf_RestInstance::getRestClient(),
                );
                $this->cache->save($cacheKey, $result, $this->cacheTTL);
            }

            $failHosts = count($result);
        } else {
            $message = sprintf('Invalid rule detected without filter condition. Rule details: %s ', json_encode($rule));
            log_message('error', $message);
        }

        $totalHosts = $this->host_model->getHostCountByContext($alert->username, $includes, $excludes);

        $alertStatus['status'] = $failHosts > 0 ? 'fail' : 'success';
        $alertStatus['failHosts'] = $failHosts;
        $alertStatus['totalHosts'] = $totalHosts;

        return $alertStatus;
    }

    /**
     * Return statistic information for list of alerts grouped by severity.
     *
     * failed and total - unique set of hosts for each group
     *
     * @param type $alertList
     *
     * @return <array>
     */
    public function getAlertStats($alertList)
    {
        $statistics = [];

        $statistics['high'] = $statistics['low'] = $statistics['medium'] = [
            'failAlerts' => 0,
            'totalAlerts' => 0,
            'failHosts' => 0,
            'totalHosts' => 0,
        ];

        if (empty($alertList)) {
            return $statistics;
        }

        // get rules - faster to get list of all rules than do this for each group
        $combinedAlerts = $this->_getCombinedRulesAndAlertsFilterByType($alertList, 'all');

        if (empty($combinedAlerts)) {
            return $statistics;
        }

        $alertsGrouped = []; // this array will have all alerts grouped by LEVEL1 => severity and LEVEL2 - includes

        // to calculate how many alerts by severity we have
        $totalAlertsBySeverity = [];
        $totalAlertsBySeverity['high'] = 0;
        $totalAlertsBySeverity['medium'] = 0;
        $totalAlertsBySeverity['low'] = 0;
        // group by severity + group by include

        foreach ($combinedAlerts as $itemIndex => $combinedItem) {
            $severiry = $combinedItem['alert']->severity;

            /**
             * @todo Change hostcontext name to includes name and add excludes name
             */
            // To group by include
            $includes = isset($combinedItem['alert']->hostContexts['name'])
                ? $combinedItem['alert']->hostContexts['name']
                : 'ALL';
            $excludes = isset($combinedItem['alert']->hostContexts['excludeName'])
                ? $combinedItem['alert']->hostContexts['excludeName']
                : 'ALL';

            // calculate number of alerts by severity
            $totalAlertsBySeverity[$severiry]++;

            $alertsGrouped[$severiry][$includes]['items'][] = $combinedItem;
            // add includes array, in order to set it for runQuery

            $alertsGrouped[$severiry][$includes]['includes'] = $combinedItem['includes'];
            /**
             * @todo Change hostcontext name to includes name and add excludes name
             */
            $alertsGrouped[$severiry][$includes]['excludes'] = $combinedItem['excludes'];
        }

        // to calculate how many FAILING alerts by severity we have
        $totalFailAlertsBySeverity = [];
        $totalFailAlertsBySeverity['high'] = 0;
        $totalFailAlertsBySeverity['medium'] = 0;
        $totalFailAlertsBySeverity['low'] = 0;
        //$priorityGroupIndex =  high/medium/low

        foreach ($alertsGrouped as $priorityGroupIndex => $priorityGroup) {
            $hostsListTotal = [];
            $hostsListFailed = [];

            foreach (
                $priorityGroup
                as &$includeGroup // different host groups - include
            ) {
                $SQLSTRING = '';

                $SQLUnionForFailed = [];

                $username = '';

                $inventoryHosts = [];
                foreach ($includeGroup['items'] as &$combinedItem) {
                    $username = $combinedItem['alert']->username;

                    // if alert is failing - add it. We know this numbers, because $alertList ash full info about alert
                    if ($combinedItem['alert']->failHosts > 0) {
                        $totalFailAlertsBySeverity[$priorityGroupIndex]++;
                    }

                    $SQLSTRING = '';

                    switch ($combinedItem['rule']->type) {
                        case 'policy':
                            $SQLSTRING = $this->policyalert->getStatSqlStringForPolicy($combinedItem['rule']);
                            break;

                        case 'inventory':
                            $SQLSTRING = $this->inventoryalert->getStatSQLStringForInventory($combinedItem['rule']);
                            break;

                        case 'softwareupdate':
                            $SQLSTRING = $this->softwareupdatealert->getStatSQLStringForSoftwareUpdate(
                                $combinedItem['rule'],
                            );
                            break;
                        case 'custom':
                            $SQLSTRING = $this->customalert->getStatSqlString($combinedItem['rule']);
                            break;
                        case 'fileChanged':
                            $SQLSTRING = $this->filechangedalert->getStatSqlString($combinedItem['rule']);
                            break;
                    }

                    if ($combinedItem['rule']->type == 'inventory') {
                        $cacheKey = md5(
                            json_encode($combinedItem['rule']->inventoryConditions['filters']) .
                                $username .
                                json_encode($includeGroup['includes']) .
                                json_encode($includeGroup['excludes']),
                        );

                        if (!($inventoryResultHosts = $this->cache->get($cacheKey))) {
                            $inventoryResultHosts = $this->inventoryalert->getStatForInventory(
                                $combinedItem['rule']->inventoryConditions['filters'],
                                $includeGroup['includes'],
                                $includeGroup['excludes'],
                                Cf_RestInstance::getRestClient(),
                            );
                            $this->cache->save($cacheKey, $inventoryResultHosts, $this->cacheTTL);
                        }

                        $inventoryHosts = array_unique(array_merge($inventoryHosts, $inventoryResultHosts));
                    } else {
                        $SQLUnionForFailed[] = $SQLSTRING;
                    }
                }

                // total hosts should use host table, abd we will limit it with context
                $SQLTotal = 'SELECT hostkey FROM v_hosts'; //implode(' UNION ', $SQLUnionForTotal);

                $cacheKey =
                    $username .
                    '_totalHosts_' .
                    implode('_', $includeGroup['includes']) .
                    implode('_', $includeGroup['excludes']);

                if (!($tmpHostkeyListTotal = $this->cache->get($cacheKey))) {
                    $tmpHostkeyListTotal = $this->_utils_getHostKeyListForAlert(
                        $username,
                        $SQLTotal,
                        $includeGroup['includes'],
                        $includeGroup['excludes'],
                    );

                    $this->cache->save($cacheKey, $tmpHostkeyListTotal, $this->cacheTTL);
                }

                $hostsListTotal = array_unique(array_merge($hostsListTotal, $tmpHostkeyListTotal));
                //create final SQL statement for failed
                $SQLFailed = implode(' UNION ', $SQLUnionForFailed);
                $tmpHostkeyList = [];
                if ($SQLFailed) {
                    $tmpHostkeyList = $this->_utils_getHostKeyListForAlert(
                        $username,
                        $SQLFailed,
                        $includeGroup['includes'],
                        $includeGroup['excludes'],
                    );
                }
                $hostsListFailed = array_unique(array_merge($hostsListFailed, $tmpHostkeyList, $inventoryHosts));
            }

            $statistics[$priorityGroupIndex]['totalAlerts'] = $totalAlertsBySeverity[$priorityGroupIndex];
            $statistics[$priorityGroupIndex]['failAlerts'] = $totalFailAlertsBySeverity[$priorityGroupIndex];

            $statistics[$priorityGroupIndex]['totalHosts'] = count($hostsListTotal);
            $statistics[$priorityGroupIndex]['failHosts'] = count($hostsListFailed);
        }

        return $statistics;
    }

    /**
     * Util function to get list of host key for alert statistic calculation
     *
     * @param <string> $username
     * @param <string> $SQLSTRING
     * @param <string> $includes
     * @param <string> $excludes
     *
     * @return <array>
     */
    private function _utils_getHostKeyListForAlert($username, $SQLSTRING, $includes, $excludes)
    {
        $result = $this->advancedreports_model->runQuery($username, $SQLSTRING, '', '', 0, 0, $includes, $excludes);

        // get hostkeys from result
        $hostsListTMP = [];

        foreach ($result['rows'] as &$hostkey) {
            $hostsListTMP[] = $hostkey[0];
        }

        return $hostsListTMP;
    }

    /// ------------------------ EMAIL  -----------------------------------------

    private function _generateFailMsg($alert, $alertStatus)
    {
        $msg =
            "\nYour alert for \"" .
            $alert->name .
            '" triggered at ' .
            getDateStatus($alertStatus['lastStatusChange'], true, true, $this->timeZone) .
            '.';
        $msg .=
            "\nIt is triggered for " .
            $alertStatus['failHosts'] .
            ' of your ' .
            $alertStatus['totalHosts'] .
            ' hosts configured for this alert.';
        // TODO HOSTLIST  with hostname or filter;

        if ($alert->reminder) {
            $msg .= "\nIt is configured to notify you every " . secs_to_h($alert->reminder) . ' while it is triggered.';
        }

        $msg .= $this->_generateLinksForMsg($alert);

        log_message('debug', 'Generate fail message for Sending Email: ' . $msg);

        return $msg;
    }

    private function _generateInternalErrorMsg($alert)
    {
        $msg =
            'Alert "' .
            $alert->name .
            '" failed due to an internal error. Please check either Mission Portal or Apache logs for more details.';

        $msg .= sprintf("\n\n Path to the Mission Portal log: %s", $this->config->item('log_path'));
        $msg .= sprintf("\n Path to the CFEngine Apache error log (standard configuration): %s ", ini_get('error_log'));

        if ($alert->reminder) {
            $msg .= "\nIt is configured to notify you every " . secs_to_h($alert->reminder) . ' while it is triggered.';
        }

        log_message('debug', 'Generate internal error message for Sending Email: ' . $msg);

        return $msg;
    }

    private function _generateSuccessMsg($alert, $alertStatus)
    {
        $msg =
            "\nYour alert for \"" .
            $alert->name .
            '" cleared at ' .
            getDateStatus($alertStatus['lastEventTime'], true, true, $this->timeZone) .
            '.';
        $msg .= $this->_generateLinksForMsg($alert);

        return $msg;
    }

    private function _generateLinksForMsg($alert)
    {
        $this->load->model('dashboard/widget_model');
        $this->load->model('dashboard/dashboard_model');

        $msg = '';

        $filter = [$alert->id];

        $widgetTmp = $this->widget_model->get_all_widgets_with_alertIds($filter);

        if (empty($widgetTmp)) {
            log_message('debug', '_generateSuccessMsg():. Error: unable to find widget for alert id: ' . $alert->id);
        } else {
            $widgetObj = $widgetTmp[0];
        }

        // Find dashboard ID that contains this widget
        $dashboardPart = '';
        if (isset($widgetObj) && !empty($widgetObj->id)) {
            // Query to find dashboard that contains this widget
            $query = $this->db
                ->select('id')
                ->from('dashboard_dashboards')
                ->like('widgets', (int) $widgetObj->id)
                ->limit(1)
                ->get();

            if ($query->num_rows() > 0) {
                $dashboardPart = '/' . $query->row()->id;
            }
        }

        $msg .= "  \n";
        $msg .= "\nCurrent state: " . $alert->site_url . '/dashboard' . $dashboardPart . '/alerts/result/' . $alert->id;

        if (isset($widgetObj) && !empty($widgetObj->id)) {
            $msg .=
                "\nPause, edit or remove: " .
                $alert->site_url .
                '/dashboard' .
                $dashboardPart .
                '/alerts/listAll?widget=' .
                $widgetObj->id;
        }

        $msg .= "\nCFEngine Dashboard: " . $alert->site_url . '/dashboard' . $dashboardPart . '/';

        return $msg;
    }

    /// ------------------------ Validation  -----------------------------------------

    public function _nameAlreadyExist($data)
    {
        $alreadyExist = false;

        if (isset($data['widgetid'])) {
            // if not a new widget
            // filter by user, alert name and widget id (widget id only disallow existing name within same widget)
            $filter = [];
            if (is_array($this->defaultFilter) && isset($this->defaultFilter['username'])) {
                $filter['username'] = $this->defaultFilter['username'];
            }
            $filter['name'] = $data['name'];
            $filter['widgetid'] = $data['widgetid'];

            $result = $this->get_only_alerts($filter);

            if (!empty($result)) {
                $alreadyExist = true;
                if (array_key_exists('id', $data)) {
                    // check if there is an id - if so, compare ids to make sure $alreadyExist is not getting value from the alert being overwritten
                    foreach ($result as $alert) {
                        if ($data['id'] === $alert->id) {
                            $alreadyExist = false;
                        }
                    }
                }
            }
        }

        return $alreadyExist;
    }

    public function _alertOwner($data)
    {
        $isOwner = false;
        $filter = $this->defaultFilter;
        $filter['name'] = $data['name'];

        $currentItem = $this->get_only_alerts($filter);

        if (!empty($currentItem)) {
            $owner = $currentItem[0]->getUsername();

            if ($owner === $data['username']) {
                $isOwner = true;
            }
        }

        return $isOwner;
    }

    //************************************
    //
    // TODO: This functions should be revieed and replaced in all project if valuable
    // right now this is a copy/paste from save_search model
    //________________________________________________________

    public function setError($errorText, $errorCode = 406, $level = 'ERROR')
    {
        $i = count($this->errors);
        $this->errors[$i]['text'] = $errorText;
        $this->errors[$i]['errorCode'] = $errorCode;
    }

    public function getErrors($level = 'ERROR')
    {
        return $this->errors;
    }

    public function clearErrors()
    {
        $this->errors = [];
    }

    private function updateCustomAlertScripts($id, $customNotificationScripts)
    {
        if (is_array($customNotificationScripts) && !empty($id)) {
            // delete all and update

            $this->db->delete('dashboard_alerts_script', ['alert_id' => $id]);
            $updateArray = [];
            foreach ($customNotificationScripts as $scriptID => $selected) {
                if ($selected == true) {
                    $updateArray[] = ['alert_id' => $id, 'script_id' => $scriptID];
                }
            }
            if (!empty($updateArray)) {
                $this->db->insert_batch('dashboard_alerts_script', $updateArray);
            }
        }
    }

    private function getAlertConditionArguments($rule)
    {
        $arguments = [];
        switch (strtolower($rule->type)) {
            case 'policy':
                foreach ($rule->policyConditions as $index => $value) {
                    $key = 'ALERT_POLICY_CONDITION_' . strtoupper($index);
                    $arguments[$key] = $value;
                }
                break;
            case 'softwareupdate':
                foreach ($rule->softwareUpdateConditions as $index => $value) {
                    $key = 'ALERT_SOFTWARE_UPDATE_CONDITION_' . strtoupper($index);
                    $arguments[$key] = $value;
                }
                break;
            case 'inventory':
                foreach ($rule->inventoryConditions as $index => $value) {
                    if (strtolower($index) == 'filters') {
                        foreach ($rule->inventoryConditions['filters'] as $filter) {
                            $filterIndex = $filter['label'];
                            $filterValue = $filter['value'];
                            $conditionValue = $filter['condition'];

                            $key = 'ALERT_INVENTORY_CONDITION_FILTER_' . strtoupper($filterIndex);
                            $conditionkey =
                                'ALERT_INVENTORY_CONDITION_FILTER_' . strtoupper($filterIndex) . '_CONDITION';
                            $arguments[$key] = $filterValue;
                            $arguments[$conditionkey] = $conditionValue;
                        }
                    }
                }
                break;
            case 'custom':
                foreach ($rule->customConditions as $index => $value) {
                    $key = 'CUSTOM_CONDITION_' . strtoupper($index);
                    $arguments[$key] = $value;
                }
                break;
        }

        return $arguments;
    }
}
