<?php
require_once 'AlertSQL.php';
class PolicyAlert extends AlertSQL
{
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Return policy status, number of fail and total hosts
     *
     * @param <CF_ALERT> $alert
     * @param <CF_RULET>  $rule
     * @param <array> $includes
     * @return <array>
     */
    public function getPolicyStatus($alert, $rule, $includes = [], $excludes = [])
    {
        $condition = $rule->getPolicyConditions();

        $escapedOutcome = $this->ci->db->escape_str($condition['promiseoutcome']);
        $WHERE_CONDITION = "WHERE (promiseoutcome ='" . $escapedOutcome . "')";

        if ($this->_buildPolicyWHERE_filterBy($condition) === '') {
            $WHERE_DATASOURCE = '';
        } else {
            $WHERE_DATASOURCE = ' AND (' . $this->_buildPolicyWHERE_filterBy($condition) . ')';
        }

        $SQL = "SELECT COUNT(distinct hostkey) as failHosts FROM
                    (
                        SELECT hostkey, promiseoutcome
                            FROM promiseexecutions
                            $WHERE_CONDITION $WHERE_DATASOURCE
                    ) AS hostkey_outcomes";

        // we expect only 1 row as result
        $result = $this->ci->advancedreports_model->runQuery(
            $alert->username,
            $SQL,
            '',
            '',
            0,
            1,
            $includes,
            $excludes,
        );

        $alertStatus = [];
        if (isset($result['rows']) && isset($result['rows'][0])) {
            $failHosts = $result['rows'][0][0];
        } else {
            // result if nothing found - Assume success for now
            $failHosts = 0;
        }

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

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

        return $alertStatus;
    }

    /**
     * Return a link to the report without adding include/exclude or hostkey.
     *
     *
     * @param <CF_RULE> $rule
     * @param string $hostkey
     * @return <string>
     */
    public function getLinkToPromiseReport_PolicyAlerts($rule, $hostkey)
    {
        $condition = $rule->getPolicyConditions();

        $WHERE_FILTER = $this->_buildPolicyWHERE_filterBy($condition);
        $WHERE_STR = empty($WHERE_FILTER) ? ' WHERE ' : ' WHERE ' . $WHERE_FILTER . ' AND ';
        $escapedOutcome = $this->ci->db->escape_str($condition['promiseoutcome']);
        $WHERE_STR .= "(promiseoutcome ='" . $escapedOutcome . "')";

        if (!empty($hostkey)) {
            $escapedHostkey = $this->ci->db->escape_str($hostkey);
            $WHERE_STR .= " AND p.hostkey ='" . $escapedHostkey . "'";
        }

        // create link str to use it in report
        $link =
            'SELECT h.hostname AS "Host name",
                        p.bundlename AS "Bundle name",
                        p.promisetype AS "Promise type",
                        p.promiser AS "Promiser",
                        p.promisehandle AS "Promise handle",
                        p.promiseoutcome AS "Promise outcome",
                        p.logmessages AS "Log messages",
                        p.changetimestamp AS "Change time"
                 FROM promiseexecutions p INNER JOIN hosts h ON h.hostkey = p.hostkey ' . $WHERE_STR;
        return rawurlencode(base64_encode($link));
    }

    /**
     * Return list of hosts for alerts. Note, we MUST have hostkey here to set proper context
     *
     * @param <CF_ALERT> $alert
     * @param <CF_RULE>  $rule
     * @param <int> $limit
     * @return <array>
     */
    public function getFailHostsList_ForPolicyAlerts($alert, $rule, $limit = 5)
    {
        $condition = $rule->getPolicyConditions();

        $WHERE_FILTER = $this->_buildPolicyWHERE_filterBy($condition);

        $WHERE_STR = empty($WHERE_FILTER) ? ' WHERE ' : ' WHERE ' . $WHERE_FILTER . ' AND ';
        $escapedOutcome = $this->ci->db->escape_str($condition['promiseoutcome']);
        $WHERE_STR .= "(promiseoutcome ='" . $escapedOutcome . "')";

        $SQL =
            'SELECT DISTINCT
                        h.hostkey, h.hostname, count(p.promiseoutcome) AS "c"
                        FROM promiseexecutions p
                        INNER JOIN hosts h on h.hostkey = p.hostkey' .
            $WHERE_STR .
            '
                        GROUP BY h.hostkey, h.hostname, p.promiseoutcome ORDER BY c DESC';

        $SQL .= ' LIMIT ' . (int) $limit;

        $includes = $this->utils_getIncludesFromAlert($alert);
        $excludes = $this->utils_getExcludesFromAlert($alert);

        $result = $this->ci->advancedreports_model->runQuery(
            $alert->username,
            $SQL,
            '',
            '',
            0,
            $limit,
            $includes,
            $excludes,
        );
        return $result['rows'];
    }

    /**
     * Return count of failed hosts
     *
     * @param <CF_ALERT> $alert
     * @param <CF_RULE>  $rule
     * @return <int>
     */
    public function getCountFailHosts_ForPolicyAlerts($alert, $rule)
    {
        $condition = $rule->getPolicyConditions();

        $SQL = $WHERE_FILTER = $this->_buildPolicyWHERE_filterBy($condition);

        $WHERE_STR = empty($WHERE_FILTER) ? ' WHERE ' : ' WHERE ' . $WHERE_FILTER . ' AND ';
        $escapedOutcome = $this->ci->db->escape_str($condition['promiseoutcome']);
        $WHERE_STR .= "(promiseoutcome ='" . $escapedOutcome . "')";

        $SQL =
            'SELECT count(DISTINCT hostkey) as "c"
                        FROM promiseexecutions ' . $WHERE_STR;

        $includes = $this->utils_getIncludesFromAlert($alert);
        $excludes = $this->utils_getExcludesFromAlert($alert);

        $result = $this->ci->advancedreports_model->runQuery(
            $alert->username,
            $SQL,
            '',
            '',
            0,
            0,
            $includes,
            $excludes,
        );
        return $result['rows'][0][0];
    }

    /**
     * Return SQL string for hostlist with count. Note, we need hostkey in order to build proper include context
     *
     * @param <CF_ALERT> $alert
     * @param <CF_RULE>  $rule
     * @return string
     */
    public function get_SQLSTRING_HostlistAndCountPromisesByCondition($rule)
    {
        $condition = $rule->getPolicyConditions();

        $WHERE_FILTER = $this->_buildPolicyWHERE_filterBy($condition);

        $WHERE_STR = empty($WHERE_FILTER) ? ' WHERE ' : ' WHERE ' . $WHERE_FILTER . ' AND ';
        $escapedOutcome = $this->ci->db->escape_str($condition['promiseoutcome']);
        $WHERE_STR .= "(promiseoutcome ='" . $escapedOutcome . "')";

        $SQL =
            'SELECT h.hostname as "Host name", count(p.promiseoutcome) as "Promises triggering"
                FROM promiseexecutions p
                INNER JOIN hosts h on h.hostkey = p.hostkey' .
            $WHERE_STR .
            '
                GROUP BY h.hostkey, h.hostname, p.promiseoutcome ORDER BY "Promises triggering" DESC';

        return $SQL;
    }

    /**
     * Return SQL string for policy rule. Use this function to calculate alert stat
     *
     * @param <CF_RULE> $rule
     * @return <string>
     */
    public function getStatSqlStringForPolicy($rule)
    {
        $condition = $rule->getPolicyConditions();

        $WHERE_DATASOURCE = $this->_buildPolicyWHERE_filterBy($condition);

        // Note: We don't need DISTINCT here because UNION will strip all duplicates
        // to get list hosts in this set  WITHOUT promise outcome
        $SQLSTRING = 'SELECT DISTINCT hostkey FROM promiseexecutions WHERE ' . $WHERE_DATASOURCE;

        // Add promise outcome to get list of failing hosts
        $WHERE = empty($WHERE_DATASOURCE) ? ' ' : ' AND ';
        $escapedOutcome = $this->ci->db->escape_str($condition['promiseoutcome']);
        $WHERE .= "(promiseoutcome ='" . $escapedOutcome . "')";
        return $SQLSTRING . $WHERE;
    }

    ///////////////////////////////////////////////////////////////////////////
    //
    //      Utils
    //
    //////////////////////////////////////////////////////////////////////////

    /**
     * Build WHERE string for policy, it does NOT include promise outcome!!!
     *
     * @param <array> $condition - policyConditions array from CF_RULE
     * @return <string>
     */
    private function _buildPolicyWHERE_filterBy($condition)
    {
        $WHERE_DATASOURCE = '';

        if (isset($condition['filterBy']) && $condition['filterBy'] !== 'all') {
            $allowedFilterBy = ['bundlename', 'promiser', 'promisees'];
            if (!in_array($condition['filterBy'], $allowedFilterBy)) {
                return '';
            }

            switch ($condition['filterBy']) {
                case 'bundlename':
                case 'promiser':
                    $escapedFilterItemName = $this->ci->db->escape_str($condition['filterItemName']);
                    $WHERE_DATASOURCE = '(' . $condition['filterBy'] . "='" . $escapedFilterItemName . "')";
                    break;

                case 'promisees':
                    $escapedFilterItemName = $this->ci->db->escape_str($condition['filterItemName']);
                    $WHERE_DATASOURCE = "('" . $escapedFilterItemName . "' = ANY(" . $condition['filterBy'] . '))';
                    break;
            }
        }

        if (!empty($condition['promisehandle'])) {
            if (!empty($WHERE_DATASOURCE)) {
                $WHERE_DATASOURCE .= ' AND ';
            }

            $escapedPromiseHandle = $this->ci->db->escape_str($condition['promisehandle']);
            $WHERE_DATASOURCE .= " (promisehandle = '" . $escapedPromiseHandle . "')";
        }

        return $WHERE_DATASOURCE;
    }
}
