<?php
require_once APPPATH . 'modules/dashboard/models/entities/CF_Rule.php';
class rules_model extends Cf_Model
{
    var $collectionName = 'dashboard_rules';
    var $variables_dictionary = 'variables_dictionary';

    var $defaultFilter;


    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');
    }

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

    function saverule($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 = array();
        $filter['username'] = $data['username'];

        if (isset($data['id']) && !empty($data['id']))
        {
            $filter['id'] = $data['id'];
        }

        if (isset($data['export_id']) && !empty($data['export_id']))
        {
            $filter['export_id'] = $data['export_id'];
        }

        $this->setDefaultFilter($filter);

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

            $filter = $this->defaultFilter;

            // if `id` exists no need to use name in filter, because name might be changed and $currentItem will be null
            if (!array_key_exists('id', $filter) && !array_key_exists('export_id', $filter)) {
                $filter['name'] = $data['name'];
            }

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

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

                // alert report owner. if this report 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
            {
                $insertData = $this->_map_for_insert($data);
                if ($id == '')
                { // not exist - insert it will return inserted item

                    $this->db->insert($this->collectionName, $insertData);
                    $id = $this->db->insert_id();
                    log_message('debug', 'Successful save rule.: ' . $id);
                }
                else
                {
                    // if exist - update, and then select inserted item
                    $this->db->where(array(
                        'id' => $id
                    ))->update($this->collectionName, $insertData);
                }
            }
            catch(Exception $e)
            {
                $message = $e->getMessage();
                log_message('debug', 'Unable to save rule. Error: ' . $message);
                throw new Exception($message);
            }

            $offset = '';
            $limit = '';
            $order = array();
            // get object from DB
            $ruleObj = $this->get_all_rules(array(
                'id' => $id
            ) , $offset, $limit, $order, $is_admin);

            if ($ruleObj)
            {
                return $ruleObj[0];
            }
        }
        // in case of validation error we return false and error can be get from getErrors();

        return false;
    }

    function _map_for_insert($params)
    {
        if (array_key_exists('policyConditions', $params)) {
            $params['policyConditions'] = $params['policyConditions'] == null ?
                '{}' :
                json_encode($params['policyConditions']);
        }

        if (array_key_exists('inventoryConditions', $params)) {
            $params['inventoryConditions'] = $params['inventoryConditions'] == null ?
                '{}' :
                json_encode($params['inventoryConditions']);
        }

        if (array_key_exists('softwareUpdateConditions', $params)) {
            $params['softwareUpdateConditions'] = $params['softwareUpdateConditions'] == null ?
                '{}' :
                json_encode($params['softwareUpdateConditions']);
        }

        if (array_key_exists('customConditions', $params)) {
            $params['customConditions'] = $params['customConditions'] == null ?
                '{}' :
                json_encode($params['customConditions']);
        }

        if (array_key_exists('fileChangedConditions', $params)) {
            $params['fileChangedConditions'] = $params['fileChangedConditions'] == null ?
                '{}' :
                json_encode($params['fileChangedConditions']);
        }

        return $params;
    }

    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_rule_name_empty'));
            return false;
        }

        if (!isset($data['type']) || trim($data['type']) == '')
        {
            $valid = false;
            $this->setError($this->lang->line('dashboard_rule_type_empty'));
            return false;
        }


        switch ($data['type'])
        {
            case 'inventory':
                if (empty($data['inventoryConditions']))
                {
                    $valid = false;
                    $this->setError($this->lang->line('dashboard_rule_inventory_data_empty'));
                    return false;
                }
                break;
            // for software updates - all fields can be empty, so no backend validation is needed
        }


        //alert if user admin or owner of this report, otherwise - do not allow to save
        if ($this->_nameAlreadyExist($data) && !(isset($data['export_id']) && $overwrite == true)) {
            $valid = false;
            $this->setError($this->lang->line('dashboard_rule_already_exists'), 422); //422 Unprocessable Entity
        }

        return $valid;
    }


    // TODO: refactor. is_admin is not in use
    function get_all_rules($filter = array() , $offset = '', $limit = '', $order = array() , $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);
        }

        $query = $this->db->where($filter)->get($this->collectionName);
        $ruleResult = $this->getResult($query);
        return $ruleResult;
    }

    function get_rules_with_ids($ids = array() , $extraFilter = array(), $order = array())
    {
        $intIds = array_map("intval", $ids);
        $this->db->where($extraFilter);
        if (!empty($intIds))
        {
            $this->db->where_in('id', $intIds);
        }

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

        $query = $this->db->get($this->collectionName);
        if (!$query)
        {
            throw new Exception("Unable to retrieve rules due to database query error.");
        }
        $ruleResult = $this->getResult($query);

        return $ruleResult;
    }

    function get_rules_with_export_ids(array $exportIds, string $userName)
    {
        $this->db->where_in('export_id', $exportIds);
        $this->db->where('username', $userName);
        if ($query = $this->db->get($this->collectionName)) {
            return $this->getResult($query);
        }
    }

    function get_all_rules_having_name($names = array(), $extraFilter = array())
    {
        $this->db->where_in('name', $names);

        if (!empty($extraFilter)) {
            $this->db->where($extraFilter);
        }

        $query = $this->db->get($this->collectionName);
        $ruleResult = $this->getResult($query);
        return $ruleResult;
    }

    function getResult($query)
    {
        $result = array();

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

        return $result;
    }

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

        $ruleFilter = array();

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

        //0. check if rule exist
        // database will take care of deleting corresponding alert

        $ruleFilter['id'] = intval($id);

        try
        {
            $this->db->trans_start();
            $return = $this->db->where($ruleFilter)->delete($this->collectionName);
            $this->deleteRuleFromReports($ruleFilter['id']);
            $this->db->trans_complete();
            return $return;
        }
        catch(Exception $e)
        {
            $this->db->trans_rollback();
            log_message('error', $e->getMessage() . " " . $e->getFile() . " line:" . $e->getLine());
            throw $e;
        }
    }

    /**
     * @param int $id
     * Delete rule references from reports
     */
    private function deleteRuleFromReports(int $id)
    {
        $reports = $this->db->query(
            "SELECT * FROM report
             WHERE reportcategory = 'compliance_report'
             AND ((advancedreportsdata->>'SQL')::jsonb->'conditions')::jsonb @> '[$id]'::jsonb"
        )->result_array();

        if (sizeof($reports) > 0) {
            foreach ($reports as $report) {
                // unpack advancedreportsdata
                $advReportsData = json_decode($report['advancedreportsdata'], true);
                $advReportsData['SQL'] =  is_array($advReportsData['SQL']) ? $advReportsData['SQL'] : json_decode($advReportsData['SQL'], true);

                //remove needed rule id from report conditions array
                $advReportsData['SQL']['conditions'] = array_values(
                    array_diff($advReportsData['SQL']['conditions'], [$id])
                );

                $advReportsData['SQL'] = json_encode($advReportsData['SQL']);
                $this->db
                    ->where(['id' => $report['id']])
                    ->update('report', ['advancedreportsdata' => json_encode($advReportsData)]);
            }
        }
    }

    function _nameAlreadyExist($data)
    {
        $alreadyExist = FALSE;
        $filter = $this->defaultFilter;
        $filter['name'] = $data['name'];
        // when ID exists (update action) filter should exclude that ID, otherwise will be false positive
        if (array_key_exists('id', $filter) && !empty($filter['id'])) {
            $filter['id != '] =  $filter['id'];
            unset($filter['id']);
        }
        $result = $this->get_all_rules($filter);

        if (!empty($result))
        {
            $alreadyExist = TRUE;
        }

        return $alreadyExist;
    }

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

        $currentItem = $this->get_all_rules($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
    //________________________________________________________

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

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

    function clearErrors()
    {
        $this->errors = array();
    }

    function validateCustomSql($sql)
    {
        $errors = [];
        try {
            $queryResult = $this->rest->post('/query', ["query" => $sql]);
        } catch (Exception $e) {
            $errors[] =  $e->getMessage();
            return $errors;
        }
        $queryResult = json_decode($queryResult, JSON_OBJECT_AS_ARRAY);

	    $isHostKey = false;
        foreach ($queryResult['data'][0]["header"] as $column){
	        if ($column["columnName"] == 'hostkey') {
		        $isHostKey = true;
	        }
        }
        if(!$isHostKey){
	       $errors[] = 'One of the select columns must be hostkey.';
        }
        return $errors;
    }

    function getRuleFailHosts(CF_Rule $rule, $username, $includes = [], $excludes = [], $fetchDataViaAPI = true)
    {
        $failHosts = 0;
        $params =  $this->getSQL($rule);

        if (!empty($params['sql'])) {
            if ($rule->conditionMustBeMet == true) {
                $sql = "SELECT COUNT(*) FROM hosts WHERE hostkey NOT IN ({$params['sql']})";
                $result = $this->advancedreports_model->runQuery($username, $sql, '', '', 0, 0, $includes, $excludes, $params['filter'], fetchDataViaAPI: $fetchDataViaAPI);
                $failHosts = intval($result['rows'][0][0]);
            } else {
                $result = $this->advancedreports_model->runQuery($username, $params['sql'], '', '', 0, 0, $includes, $excludes, $params['filter'], fetchDataViaAPI: $fetchDataViaAPI);
                $failHosts = sizeof($result['rows']);
            }
        }

        return $failHosts;
    }

    public function getResultSQL(CF_Rule $rule, $username)
    {
        $params = $this->getSQL($rule);
        $sql = $params['sql'];
        if (sizeof($params['filter']) > 0) {
            $result = $this->advancedreports_model->runQuery($username, $params['sql'], '', '', 0, 1, [], [], $params['filter']);
            $sql = $result['query'];
        }
        return $sql;
    }

    public function getSQL(CF_Rule $rule)
    {
        $sql = '';
        $filter = [];
        switch ($rule->type) {
            case 'policy'    :
                $sql = $this->policyalert->getStatSqlStringForPolicy($rule);
                break;
            case 'inventory' :
                $sql = 'SELECT hostkey FROM hosts INNER JOIN inventory_cte ON inventory_cte.filtered_hostkey = hosts.hostkey';
                $filter = $this->inventoryalert->getFilterForInventory($rule->inventoryConditions['filters']);
                break;
            case 'softwareupdate' :
                $sql = $this->softwareupdatealert->getStatSQLStringForSoftwareUpdate($rule);
                break;
            case 'custom' :
                $sql = $this->customalert->getStatSqlString($rule);
                break;
            case 'fileChanged' :
                $sql = $this->filechangedalert->getStatSqlString($rule);
                break;
        }

        return [
            'sql' => $sql,
            'filter' => $filter
        ];
    }

    public function generateExportId(CF_Rule $rule, $username)
    {
        $newExportId = strtolower(str_replace(' ', '-', $rule->name));

        $ruleWithSameId = $this
            ->get_all_rules(
                $filter = ['export_id' => $newExportId, 'username' => $username],
                $offset = 0,
                $limit = 1,
                $order = ['export_id', 'DESC']
            );

        if ($ruleWithSameId) {
            $newExportId = $newExportId .'-'. (intval(preg_replace('/[^0-9]/', '', $ruleWithSameId[0]->export_id)) + 1);
        }

        $sql = 'UPDATE dashboard_rules SET export_id = ? where id = ?';
        $this->db->query($sql, [$newExportId, $rule->id]);
        return $newExportId;
    }

    // END
    // ________________________________________________________

}